Repository navigation
bug(telemetry): crash recorder logs designed refusals and tool exits as crashes #158
Description
Activity
Scope correction after checking where the recorder actually lives: this is not fixable in this repository.
The crash recorder is SDK-side:
$ grep -rl "gjc-crash-record\|crash-index" node_modules/@gajae-code/coding-agent/src node_modules/@gajae-code/coding-agent/src/crash/upstream/relay.ts $ grep -rn "gjc-crash-record\|crash-index\|gjc-error-events" server/ shared/ src/ scripts/ (no matches)Classification, fingerprinting and the
~/.gjc/agent/gjc-error-events.jsonlwrite all happen inside@gajae-code/coding-agent. The app can setcrashReport.upstream/crashReport.upstreamDsn/gjc.crash, but none of those change what counts as a crash — they only control upstream relay.The measurements stand and are worth carrying upstream as-is:
- 1,992 events, 1,259 unique fingerprints (63% unique — the index dedupes almost nothing)
- 580 of them are designed refusals: user tool declines (154), delegation allowlist denials (149), ralplan phase boundaries (140), ultragoal nudges (137)
- a
cargo testexit 101 recorded with a pure-SDK stack trace, fixed 27s later by the same session
Keeping this open as an upstream tracking issue rather than an app bug. No app PR will follow.
Verified 2026-09-19 against the installed (lifecycle-patched)
@gajae-code/coding-agent0.16.4 and the latest published 0.17.2 (npm-pack compared). Real, and unchanged in 0.17.2.Mechanism confirmed in source
agent-core/src/telemetry.ts:1883— every tool span that endsstatus === "error"callsrecordHandledError(\Tool ${toolName}`, errorObject)`. No class of tool error is excluded.session/agent-session.ts(0.16.4 :10242 / 0.17.2 :10367) — a user rejection is thrown asToolError("Tool call rejected by user (…)"), so the designed refusal is an error-status span.tools/output-meta.tswrappedExecuterethrows tool failures (incl. non-zero command exits) asnew Error(renderError(e))with an SDK-internal stack, so the record's trace points atoutput-meta.ts/wrapper.ts, never at the user's command.@gajae-code/utils/postmortem.tsrecordHandledErrorrecords anyErrorwith a stack and fingerprints name+message+stack together — hence near-unique fingerprints.
Live repro (installed 0.16.4, agent dir redirected to a temp dir)
Feeding the recorder the exact error shapes the session throws produced:
gjc-crash-event.v1 {"n":"ToolError","m":"Tool call rejected by user (bash)","fp":"0500aca5…"} gjc-crash-event.v1 {"n":"ToolError","m":"Command exited with code 101 error: could not compile `gajae-core` (lib test)","fp":"080b84ec…"} gjc-crash-event.v1 {"n":"ToolError","m":"Tool call rejected by user (read)","fp":"9973e5bb…"}3 events, 3 distinct fingerprints — two of them designed refusals differing only by tool name.
gjc-error.loggot the matchinggjc-crash-record.v1records with all-SDK stacks. As a bonus, an accidental unhandledSyntaxErrorin the repro script landed in the fatalgjc-crash.log: fatal and designed-refusal evidence share one store with nothing distinguishing them.0.17.2 comparison
telemetry.tscall site,postmortem.tsrecordHandledError(diff clean), and the rejectionToolErrorthrow are all byte-identical in 0.17.2. Nothing in the 0.16.4 → 0.17.2 delta filters refusals, allowlist denials or tool exits out of the crash store.Ready to carry upstream as-is; the Expected list in the issue body is still accurate against 0.17.2.
Filed upstream after verification against dev @ 2026-09-19 (all cited code paths identical there): Yeachan-Heo/gajae-code#5718
- added a commit that references this issue
on Sep 19, 2026 Rechecked on the published 0.17.6 (CLI binary and app SDK) after #176, against a running build.
- Fixed upstream (#5743): a permission denial and a bash command that exits non-zero are no longer recorded. Checked live through the app: one allowed
ls /definitely/missing/pathand one denied call, zero new occurrences. - Still recorded: hook refusals (
{ block: true }becomes a plainErrorin the extension wrapper, so our delegation allowlist refusal appears 8× in six hours),EditMatchError,readof an unknownembedded:path andShell runtime exited with code 70. 12 records, none a real fault.
Filed upstream as Yeachan-Heo/gajae-code#5938. Separately, 8 of those 12 came from this repo's own Bun test suite writing into the operator's real
~/.gjcjournal; the test runner now gives every Bun test file a throwawayHOME(PR to follow). Keeping this open until the upstream follow-up lands.- Fixed upstream (#5743): a permission denial and a bash command that exits non-zero are no longer recorded. Checked live through the app: one allowed
The crash recorder writes a fingerprinted crash record for ordinary, expected outcomes — user tool refusals, delegation allowlist denials, workflow phase boundaries, non-zero shell exits, rejected edit anchors. Real crashes are buried and the fingerprint index stops deduplicating anything.
Evidence
~/.gjc/agent/gjc-error-events.jsonlon 2026-09-17:~/.gjc/agent/gjc-error.logis 341 KB;gjc-error-events.jsonlis 622 KB.Top recorded "crash" messages:
Bucketed by cause:
Two concrete misclassifications
A designed refusal is not a crash.
Tool call rejected by user,Tool is outside the parent delegation allowlist, and the ralplan/ultragoal phase-boundary messages are the system working correctly. That is 580 of 1,992 events.A tool's non-zero exit is not a crash. A
cargo testcompile failure was recorded with a full SDK stack trace:The session fixed that compile error 27 seconds later and moved on. The record remains, indistinguishable from a real fault, and its stack trace points at SDK internals rather than at
git.rs.Same for a read-only SQL typo from an interactive session:
Why this is a problem
gjc-crash-index.jsonsurfaces "recent 21 event ids". With this signal-to-noise, those 21 are recent noise, not recent crashes.cargoexit above as an ego/SDK crash on first pass precisely because the trace is all SDK frames.Expected
Do not create a crash record for:
Keep the crash path for unhandled exceptions and invariant violations. Fingerprint on the failing frame, not on the message text, so recurrence actually collapses.