fix(claude): guard cost/health bookkeeping so a mid-stream claude exit can't crash the tick (#272) #276
No reviewers
Labels
No labels
Agent
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Total time spent: 1 minute 15 seconds
Due date
igor
1 minute 15 seconds
No due date set.
Dependencies
No dependencies set
Reference
joshtronic/igor!276
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/claude-cost-record-crash"
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?
Fixes the overnight #272 crash — and corrects its premise: it was never OOM.
Evidence it was not memory: 31 GiB RAM (27 free, swap untouched), exit code 1 not 137 (an OOM-kill is SIGKILL→137), and zero kernel OOM-killer entries. The 822M peak was just Chromium/Playwright's normal footprint.
The actual bug: after the claude pipe,
claude_run_with_costre-enablesset -eand callscost_record_cliunguarded. When claude exits nonzero mid-stream, the stream JSONL is truncated → the cost parse fails →set -eturns that into a rawstatus=1that exits the whole tick beforereturn $rc, bypassing the caller'sset +eguard and itsclaude exited Nhandling. So a recoverable model crash became a unit FAILURE + a from-scratch redo (exactly the "exit 1, noclaude exitedline" signature in the journal).The else-branch health classification was already
|| trueeverywhere ("never kill the tick") —cost_record_cliand the rc==0claude_health_record_okwere simply missed. Now both are guarded.bash -n+ check-sync green. No unit test (the claude invocation isn't a sourceable seam, same as the worktree/tick paths). Closes #272. No reviewer.🤖 Review —
APPROVE(automated)CI for
7f0e8ef9: successSummary: Adds two
|| trueguards inclaude_run_with_cost(cost_record_cliand the rc==0claude_health_record_ok) so a mid-stream claude crash that truncates the stream JSONL can't turn a bookkeeping failure into aset -eexit that bypasses the caller'sclaude exited Nhandling. Correct, tightly scoped, and consistent with the existing "bookkeeping never kills the tick" design.Findings:
rcis captured fromPIPESTATUS[0]before the guards, andreturn $rclater preserves claude's actual exit code. The guards only suppress failures in the two non-critical bookkeeping side-effects, which is exactly the intent. The change can only make the path more error-tolerant — it has essentially no regression surface.set -esemantics check out:cmd || trueis a tested compound command, so neither bookkeeping call can abort the function, and the swallowed nonzero status is intentional.Caveats I'm trusting (non-blocking):
claude_run_with_costnot being a sourceable/mockable seam (the real claude invocation precedes the guarded lines), consistent with the worktree/tick paths. Plausible, but unverifiable from the diff — if the function were sourceable with a stubbedcost_record_cli, aset -e-doesn't-exit test would be the ideal regression guard for a bug that is fundamentally aboutset -ebehavior. Worth keeping in mind for the next time this path is refactored.|| trueeverywhere," but the in-code comment corroborates it and, since this change is purely additive guarding, that claim doesn't affect this diff's correctness.CI is green; change is safe to merge as-is.
Independent review by the harness on
claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.