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).
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 structuredEdit,MultiEdit,Write, orapply_patchtool calls. -
78 mutations (
39.2%) went throughBashorcodex:execshell 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:
-
Read-only
python3andsedinspections (LPB006filters 570 calls): My initial regex flaggedsubmission.pyas mutated 32 times in one session. When I read the transcript, 30 of those commands were read-onlypython3one-liners (open('submission.py').read()) andsedprint 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. -
.jsonlvs.jsprefix collisions & doc updates (LPB007filters 218 hits): Without a trailing word boundary\bafter file extensions, a regex matching\.(js|py|ts)matches.jsonlas.js, accusingjournal.jsonlof being a JavaScript source file 207 times. Normal end-of-task updates toREADME.mdandpackage.jsonaccounted for another 11 edits that should not trigger code-thrash alerts. -
Streaming
tool_use.id/call_idduplication (LPB014): Claude Code writes multiple JSONL progress lines sharing the sametool_use.idwhile 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): In5f37b3d3.jsonl, the agent invokedSkill("pony tail")(with a space instead ofponytail) 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 printedWorking directory ... was deleted; shell cwd recovered to /Users/...and the agent immediately ranrgacross the home directory. -
Identical command retries & empty
write_stdinspins (LPB011,LPB012): 20 streaks of>= 3identical consecutive shell commands (48 wasted calls) and 3 sessions spinning on emptywrite_stdinpolls.
Try loopbrake on your own transcripts
I packaged the auditor and hook generator as loopbrake (zero dependencies, Node.js >= 18):
npx loopbrake
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)