Claude Code rewrites each assistant message several times as it streams,
every copy carrying the full final usage; the shipped parser dedupes these
last-wins (dedupeStreamingMessageIds) but the guard fold summed every line,
measuring real sessions at 2.5-2.8x their true cost and false-blocking the
hard cap at roughly 40% of the configured spend.
- The session cache now maps message id -> that id's cost contribution and
each id-carrying line replaces its previous contribution; id-less lines
keep plain adds. Validated against two real transcripts (90MB and 116MB):
guard totals now equal the shipped deduped totals exactly.
- Replace semantics also self-heal the trailing-line case: a complete final
line without its newline is folded but byteOffset stops before it, so the
next invocation re-reads it as a replace, not a double add.
- editCount becomes a set-once sawEdit boolean so duplicate copies of an
edit tool_use cannot inflate it; cache schema bumped to v2 (old caches
cold-reparse once).
- Per-session state moves to guard/sessions/ so a session id can never
collide with the shared flags.json, dropping the doAllow special case.
- The git-commit detector now requires commit as the git subcommand at a
command boundary (start of string or line, or ; & |), with intra-command
gaps that never cross newlines: 'git log --grep commit' and
'git diff && echo commit' no longer match, while newline-separated
'git add ...\ngit commit' in multi-line Bash calls now does (verified as
a real false negative on a live transcript).
- Corrected the statusline protocol note: each stdout line renders as its
own row; we emit exactly one.
- New tests: streaming-duplicate fixtures (3x identical, growing last-wins,
incremental replace) asserted equal to a cold shipped-parser computation,
the trailing-partial-line scenario, the commit-detector matrix, and a
stale-plan test proving guard-install plans carry expectedHash (a
concurrent settings edit aborts the apply and survives). The act list CLI
spawn test now anchors to the repo root from the test file location.
codeburn guard install|uninstall|status|refresh|allow plus the internal
hook/statusline handlers. Off by default, fully local, cleanly removable.
- Settings edits go through the action journal (guard-install / guard-uninstall)
with expectedHash, appending our entries and removing exactly ours by command
prefix; a byte-identical uninstall is asserted by test.
- PreToolUse budget cap (soft warn once, hard block with a per-session allow
override), Stop yield checkpoint (expensive with no edits and no commit, once),
and a flagged-project SessionStart opener built from the optimize detectors.
- Incremental per-session cache keyed by session id: resumes the transcript
parse from the last complete-line byte offset via readSessionLines, folding
only the tail into running totals. Warm invocation ~0.28s against a 90MB
transcript, dominated by CLI startup; the tail parse itself is negligible.
- All handlers fail open: any error, malformed stdin, or missing transcript
exits 0 with no output so a broken guard can never block a session.
- Hook protocol verified against the live docs (dated block at the top of
hooks.ts); zero new dependencies.