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.jsonl on 2026-09-17:
events: 1,992 unique fingerprints: 1,259 (63% unique)
kinds: {'occurrence': 1992}
names: Error 1823, ToolError 165, AbortError 4
~/.gjc/agent/gjc-error.log is 341 KB; gjc-error-events.jsonl is 622 KB.
Top recorded "crash" messages:
154 Tool call rejected by user (bash)
149 Tool is outside the parent delegation allowlist.
140 Extension <inline-0> failed: Ralplan planning phase boundary: keep refining the conse…
137 Ultragoal try-harder nudge (1/10) for G001: asking the user was refused before the no…
26 Edit rejected: N anchors do not match the current file (marked *). The edit was NOT a…
15 Working directory does not exist: <path>
12 artifact:<path> URLs cannot be expanded in bash commands…
Bucketed by cause:
other (mostly designed refusals, above) 1,709
shell/command exit 192
test assertion failure 50
network/timeout 31
sqlite query error 7
cargo/compile failure 3
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 test compile failure was recorded with a full SDK stack trace:
error: could not compile `gajae-core` (lib test) due to 3 previous errors
--> src/git.rs:1233:21
None => unsafe { std::env::remove_var("HOME") },
Command exited with code 101
at wrappedExecute (…/@gajae-code/coding-agent/src/tools/output-meta.ts:1134:13)
at async execute (…/src/extensibility/extensions/wrapper.ts:142:29)
at async <anonymous> (…/src/session/agent-session.ts:10258:26)
…
gjc-crash-record.v1 fp:fb459a7162be028fb05d2e7690694317
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:
[Tool read] Error: no such column: id
gjc-crash-record.v1 fp:38cbb9a4f7b08abc427959d1e5334b0e
Why this is a problem
- 63% of events carry a unique fingerprint. A deduplicating index that dedupes almost nothing is just an append log with extra steps.
gjc-crash-index.json surfaces "recent 21 event ids". With this signal-to-noise, those 21 are recent noise, not recent crashes.
- The stack traces mislead. I misread the
cargo exit 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:
- user-declined tool calls,
- policy/allowlist denials and workflow phase boundaries,
- non-zero exit from a tool that reports command failure through its own result channel,
- rejected edits whose anchors did not match.
Keep the crash path for unhandled exceptions and invariant violations. Fingerprint on the failing frame, not on the message text, so recurrence actually collapses.
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.