Build1 publisher3 min readPublished Updated
The stop hook that ended five failed corrections would block the corrected command too
Moving a rule out of CLAUDE.md and into an exit code stopped the reoffending, but the 46-line guard that replaced it decides handoff syntax by regex, without knowing whether the command it inspects ever returns.
The Engineer · Build desk

What happened
- A developer writing on dev.to reports telling Claude Code more than five times not to chain ! handoff commands with &&, getting "Understood" each time, and seeing the same command return the next session.
- The underlying hazard is that && runs the next command only after the previous one exits, so chaining it after a blocking server start leaves the session waiting and frozen.
- The replacement is a 46-line Stop hook registered in .claude/settings.json that reads the last assistant message and blocks the response when a line beginning with ! contains &&.
- When the transcript path is absent or no file exists at it, the script exits 0 and lets the response through, a fail-open default the author documents explicitly.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- contradiction The post attributes the failure to session independence, yet also states that CLAUDE.md is injected every time, so the rule was present at every turn and still lost to volume; what the hook changes is enforcement, not persistence.
- constraint A post-generation text check can only enforce what it can parse, so the rule that actually ships is broader than the hazard, and teams adopting it inherit false positives on legitimate commands.
- cost Adoption buys a dependency on a hardcoded interpreter path plus a transcript parse on every response, and when that dependency breaks the protection ends without any signal that it has.
The check strips code fences and backticks from each line of the last assistant message, then flags any line matching `^!\s` that also contains `&&` [13]. Feed it the author's own corrected command, `! npx wrangler dev --local & sleep 2 && open http://localhost:8787` [9]. That line starts with `!` and contains `&&`, so as described the guard rejects the fix along with the bug [19].
The condition that actually breaks is semantic. `&&` runs the next command only after the previous one exits successfully, which is exactly why it can never follow a process that does not exit [6]. No regex can decide whether `npx wrangler dev --local` returns. So the rule that ships bans the token the script can see rather than the state it cares about. Either the shipped regex is narrower than the prose describing it, or the author is writing handoff lines his own hook refuses. Resolve that before copying the pattern.
Three lines in Block 1 decide whether the guard runs at all: `input=$(cat)`, then `/usr/bin/python3` reads `transcript_path` off stdin with its stderr redirected to `/dev/null`, then `[ -n "$tpath" ] && [ -f "$tpath" ] || exit 0` [15]. Any failure in that pipeline produces an empty `tpath` and a pass [20]. For something that gates every single response, that is the right default; a guard that blocks when it breaks takes the session with it. It does mean the property on offer is "blocks while working," which is weaker than the physical impossibility the post claims [24].
The firing point sets the scope. The hook launches the moment Claude finishes generating [11], and exit 2 stops the response before it reaches the user [10], so what is guarded is the outgoing text rather than anything already executed during the turn [21]. The flow also collects every assistant message before keeping only the last [16], which means each response pays a JSONL parse that grows with the session [23].
Claude answered "Understood" every time the rule came up [2], the least informative signal in the loop. The exit code adds noise on failure: a violation now halts and prints to stderr [13] instead of shipping quietly. The author reports the mistake has not recurred since [18], and reports 1.2 million yen a month from the environment built around this style of guard, twice the 600,000 yen peak he cites from before a layoff [17][22]. None of that is independently verifiable, and the mechanism does not depend on it. In my context I would take the hook and narrow the match to `&&` preceded by a known blocking binary, because a guard that rejects the sanctioned form is a guard someone switches off inside a week.
What to watch
- Whether the published bang_handoff_amp_guard.sh regex is narrower than the prose description, for example anchored to a list of blocking binaries, which would remove the false positive on the corrected form.
- Whether Claude Code keeps passing transcript_path on stdin to Stop hooks, since the guard's extraction and its fail-open path both depend on that key.
- Whether the same author moves the check to a pre-execution hook, which would cover commands run during the turn rather than only the response text.