Skip to content

bug(telemetry): crash recorder logs designed refusals and tool exits as crashes #158

Description

@devswha

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions