feat(o11y): drain Claude Code transcripts for tool-use failures (is_error) into the failure-record buffer #249
Labels
No labels
burndown-2026-06
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/agentic-os#249
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Claude Code agent tool-use failures need to land in the same failure-record buffer as Goose (issue A), so the o11y feed is harness-complete.
Key finding (empirical, this session)
Claude Code hooks cannot see tool failures, verified with a throwaway PreToolUse+PostToolUse probe:
Bash(exit 7), a no-matchgrep, and a missing-fileReadeach produced only a PreToolUse row and no PostToolUse.exit_code-tool_responseis{stdout, stderr, interrupted, isImage, noOutputExpected}. No exit code, no success flag.Edit(old_string not found) produced zero events, not even PreToolUse. Client-validated tool errors bypass the hook pipeline.The real source is the session transcript JSONL at
~/.claude/projects/<munged-cwd>/<session-id>.jsonl. Every tool result carries a uniformis_error: trueflag across all tools, and the Bash exit code is embedded in the error text (Exit code 7). The probe confirmed all four failures (including the Edit that fired no hook) appear there - 4 errors of 21 tool results.Scope
~/.claude/projects/**/*.jsonl, track a per-file watermark (byte offset or last-seen record id), extract records whereis_error == true(acrossuser-roletool_resultcontent blocks), including subagent/sidechain transcripts.harness="claude",source="claude_transcript",failure_classderived (parseExit code N,<tool_use_error>, file-not-found, MCP error),tool= the tool name,exit_codeparsed when present,stderr_excerpt/detailfrom the error content,fingerprintover the same fields.~/.cache/agentic-os/tool-failures/<repo-slug>.jsonl. No network here (issue C ships it).grep/rgno-match,test,diff,... || true) so genuine failures are not buried. The buffer may keep everything; emission (issue C) gates on genuine.Trigger
A
SessionEnd(orStop) ward hook fires the drain for that session, with a periodic watermark sweep as backstop. No live tailing - failure telemetry tolerates minute latency, and the watermark sweep is simpler and fully decoupled from the hot path. (Depends on ward gaining a non-PreToolUse hook surface.)Done condition
is_errortranscript records into failure-records in the buffer, idempotently (watermark-respecting).is_errorshape on existing on-disk transcripts first).Depends on
🔁 backlog-loop dispatch - this issue was auto-dispatched by the supervised backlog loop.
When you finish, your final issue comment must start with exactly one of:
WARD-OUTCOME: done- landed/merged, nothing more needed.WARD-OUTCOME: blocked - <the specific decision or information you need from a human>WARD-OUTCOME: failed - <why, briefly>Put your candid retrospective on the line(s) below it. The supervising loop reads this
line to decide whether to close you out, ask a human to unblock you, or retry. If you
are blocked, be concrete about the single thing you need - that is what gets answered.
🔒 Reserved by
ward agent --driver claude— containerward-agentic-os-issue-249-claude-555d9c90on hostkais-macbook-pro-2.localis carrying this issue (reserved 2026-06-25T09:51:20Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.— Claude (she/her), via
ward agentWARD-OUTCOME: done
Smoother than I expected, mostly because issue A had already laid the schema-v1 rails - I got to mirror Goose's failure-record contract rather than invent one, and reuse its
_stderr_signatureso fingerprints collapse the same way across harnesses.The transcript shape fought back a little. The tool name isn't on the error record, it's on the preceding assistant
tool_useblock, so the drain has to carry atool_use_id -> name/inputmap as it scans. And the watermark has a subtlety: I parse the whole file to build that map but only emit errors past the byte offset, otherwise a re-sweep would either miss tool names or double-write. My first truncation test was wrong (same-length rewrite never tripped the reset) - the test caught my own sloppiness before I shipped it.Nice validation moment: the dry-run against real on-disk transcripts caught a hook-invisible
Editfailure from earlier in this very session - exactly the class of failure the issue says hooks can't see. So the premise held up empirically, not just on paper.Confidence is high on the drainer and classifier (24 focused tests, 456 in the suite green). Two honest rough edges worth a follow-up: (1) the
SessionEnd/Stophook wiring is still deferred - it depends on ward gaining a non-PreToolUse hook surface, so today this is a periodic sweep only; (2) I only hadis_error: falserecords on disk to test the real MCP-error and Bash-exit-code shapes against - those paths are covered by synthetic fixtures built from the issue's empirical findings, not yet a live failing MCP call. Worth a quick confirmation pass once genuine failures accumulate in a buffer.