You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(collector): bound emit failure memory and word network failures accurately (#6)
* fix(collector): bound emit failure memory and word network failures accurately
Follow-up to the emit-feedback diagnostics (#5), addressing two Copilot
review notes:
- Cli.ts kept every failed EmitResult in an array but only ever read the
count and the last one. On a run that emits per onProgress, repeated
failures grew that array unbounded. Replace it with a counter plus the
most recent failure.
- The emit diagnostic said the collector "rejected" the emits even when
the POST never reached it (connection refused/timeout/DNS — no HTTP
status). Word an HTTP rejection and a delivery failure distinctly.
- Collector.emit returned the full rejection body, which a caller retains;
a large error page could bloat memory. Cap the stored body at 10k chars
(the log line still truncates to 500 independently).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(trace): make empty/aborted runs fail loudly instead of silently "running" (#7)
A chrome run that captured nothing and recorded nothing was reported as
success (ok:true, exit 0) and could sit on the dashboard's "running" badge
forever — the agent had no signal it was broken. Root cause: the failure
signals lived in stderr logs or dashboard-only state, never in the JSON
envelope the agent reads. This closes those gaps and the matching violations
of the logger's own two-channel contract (codes.ts: the stderr log and the
envelope diagnostic for one event should carry the SAME code).
- Terminal envelope on abort. If a run throws (attach failed, engine crashed,
recording threw), DynamicCommand now emits a terminal envelope via onProgress
— no `running` flag, ok:false, an ENGINE_FATAL diagnostic — so the collector
resolves the session instead of leaving its initial "running" partial
orphaned. Cli flushes that emit before exiting non-zero.
- Recording outcomes are now envelope diagnostics, not just stderr. RECORD_EMPTY
(no frames → empty video) and RECORD (render/upload threw) were log-only, so
"no video" was invisible to a --json reader. Both now push a warn diagnostic
carrying the same code as the log.
- Upload failure no longer masquerades as a clean local save. S3ArtifactStore
swallows failures and returns null, which #record could not tell apart from
"no S3 configured". It now distinguishes the two and emits an UPLOAD
diagnostic when a configured upload fails (link missing, local copy kept).
- ENGINE_FATAL is now logged as well as diagnosed, so it appears in both
channels per the contract.
Adds test/dynamic-diagnostics.test.js (fake-tracer unit tests for the abort
terminal envelope, captured-fatal, and clean-empty cases). typecheck + build
clean; 44/45 tests pass (1 DB round-trip skipped).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(collector): bound the rejection body at the stream, add emit-diagnostic tests
Addresses the two Copilot review notes on the PR:
- Collector.emit buffered the whole rejection body via response.text()
before slicing, so an oversized error page still caused a transient
memory spike and download latency even though the stored body was
capped. Read only up to MAX_BODY_CHARS off the stream, then cancel it —
bounding memory and latency at the source.
- Extract the emit-failure diagnostic wording into an exported
emitFailureMessage() helper and cover it with focused tests: HTTP status
→ "rejected", no status (network) → "failed" (the regression guard so a
POST that never landed isn't reported as a rejection), plus the
no-body / missing-error / oversized-body edge cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(collector): flush the TextDecoder so a split multi-byte char isn't dropped
readCappedText decoded chunks with { stream: true } but never did a final
decode() flush. A response ending on a multi-byte UTF-8 sequence split
across the last chunk boundary would drop/garble that trailing character in
the stored body and logged reason. Flush after the read loop.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
// Both channels carry the same code: the stderr trail AND an envelope diagnostic, so an agent reading
180
+
// --json learns the chrome run produced no video (instead of inferring it from a missing `recording`).
181
+
log.warn("no frames captured — nothing to record",{code: Code.RECORD_EMPTY, sessionId });
182
+
trace.diagnostics.push(Diagnostic.warn(Code.RECORD_EMPTY,"no frames captured — the debug-replay video is empty (no breakpoint hits, or the journey produced no frames)."));
// S3 WAS configured but upload() returned null → it failed (the error was logged inside the store).
192
+
// The video is still saved locally, but the dashboard gets no link — surface that instead of
193
+
// reporting a clean local save, so "no video link" isn't silently indistinguishable from success.
194
+
log.warn("recording upload failed — keeping local copy",{code: Code.UPLOAD, sessionId,path: videoPath});
195
+
trace.diagnostics.push(Diagnostic.warn(Code.UPLOAD,`recording upload failed — video saved locally at ${videoPath}, no dashboard link (check S3_ENDPOINT / credentials).`));
196
+
}else{
197
+
log.info("recording saved locally — set S3_ENDPOINT to upload + get a link",{ sessionId,path: videoPath});
198
+
}
158
199
}catch(error: any){
200
+
// Render or upload threw. Surface it in the envelope too (a warn — the trace data is still valid, only
201
+
// the replay is missing) so "no video" is never silent. Previously this was a stderr log the agent never saw.
0 commit comments