DEV Community

Cover image for Why Edit-Only Circuit Breakers Miss 39.2% of Agent File Rewrites (Auditing 153 Claude Code & Codex Logs)
Prince Jain
Prince Jain

Posted on

Why Edit-Only Circuit Breakers Miss 39.2% of Agent File Rewrites (Auditing 153 Claude Code & Codex Logs)

When I wired a PreToolUse counter on Edit and Write to stop Claude Code from patching the same file five times in a row, the agent routed around it through Bash heredocs and Python .write_text() calls.

To see how often that actually happens in real work, I parsed all 153 local Claude Code (~/.claude/projects) and Codex CLI (~/.codex/sessions) JSONL transcripts on my machine (2,581 deduplicated tool calls).

loopbrake terminal demo

1. Why Edit-only circuit breakers miss 39.2% of file rewrites

Across those 153 sessions, agents performed 199 total source-file mutations:

  • 121 mutations (60.8%) went through structured Edit, MultiEdit, Write, or apply_patch tool calls.
  • 78 mutations (39.2%) went through Bash or codex:exec shell commands (cat <<'EOF' >, sed, tee, .write_text(, open(..., "w")).

In session 01a12a24.jsonl, Codex rewrote brain.py 4 times in a single session purely through shell commands (LPB001 + LPB004). Any hook that only watches Edit and Write never fires on those rewrites. Meanwhile in session 01a1784f.jsonl, fno.py was patched 6 times, including overlapping hunks that reverted lines introduced two turns earlier (LPB003).

2. Three false-positive traps that broke my first parser

My first pass at counting file mutations produced wildly inflated numbers until I inspected the raw JSONL rows:

  1. Read-only python3 and sed inspections (LPB006 filters 570 calls): My initial regex flagged submission.py as mutated 32 times in one session. When I read the transcript, 30 of those commands were read-only python3 one-liners (open('submission.py').read()) and sed print ranges inspecting AST nodes and line numbers. Separating read-only inspections from actual write operators (>, tee, cat <<EOF, .write_text(, open(..., 'w')) removed 570 false positives.
  2. .jsonl vs .js prefix collisions & doc updates (LPB007 filters 218 hits): Without a trailing word boundary \b after file extensions, a regex matching \.(js|py|ts) matches .jsonl as .js, accusing journal.jsonl of being a JavaScript source file 207 times. Normal end-of-task updates to README.md and package.json accounted for another 11 edits that should not trigger code-thrash alerts.
  3. Streaming tool_use.id / call_id duplication (LPB014): Claude Code writes multiple JSONL progress lines sharing the same tool_use.id while streaming a single tool execution. Deduplicating by call ID is mandatory before counting consecutive retries or mutations.

3. Tool-error doom loops & deleted-sandbox CWD escapes (LPB008 to LPB012)

On the execution-loop plane, the 153 transcripts contained 178 failed tool calls grouped into 18 consecutive error streaks:

  • 9-turn consecutive tool-error doom loop (LPB008): In 5f37b3d3.jsonl, the agent invoked Skill("pony tail") (with a space instead of ponytail) 9 times consecutively, receiving <tool_use_error>Unknown skill: pony tail. Did you mean ponytail?</tool_use_error> every single time without changing the argument.
  • Deleted-sandbox CWD escape (LPB010): In 2 sessions, a temporary git worktree was deleted while the agent was still active. The shell printed Working directory ... was deleted; shell cwd recovered to /Users/... and the agent immediately ran rg across the home directory.
  • Identical command retries & empty write_stdin spins (LPB011, LPB012): 20 streaks of >= 3 identical consecutive shell commands (48 wasted calls) and 3 sessions spinning on empty write_stdin polls.

Try loopbrake on your own transcripts

I packaged the auditor and hook generator as loopbrake (zero dependencies, Node.js >= 18):

npx loopbrake
Enter fullscreen mode Exit fullscreen mode

What threshold of consecutive edits or tool errors do you use before forcing your coding agent to stop and re-read the module from scratch?

Top comments (0)