The shell-loop rule reads prose as a loop, and teaches the reflex that defeats it
What fires
.claude/hooks/no-full-suite.sh rule 3 denies when both hold:
grep -Eq '(test|tests)/[A-Za-z0-9_]*\*[A-Za-z0-9_]*\.(npy|pas|c|py|zig|rs)'
grep -Eq '(^|[;&|(]|[[:space:]])(for|while|xargs|parallel)([[:space:]]|$)|find[[:space:]]+.*-exec'
Neither is anchored to a command position. Measured 2026-09-04, two denials in one session, neither of them a suite run:
- A python heredoc doing a text census.
for i, ln in enumerate(...)is aforat a word boundary and the scan namedtest/*.pas. It reads files and compiles nothing — about a second. - A heredoc writing a TICKET. The prose said
test/*.pas(naming the population it had measured) and, elsewhere, "for a wide margin". Writing a markdown file was refused as a regression sweep.
The first denial that session was a genuine compile sweep and the rule was right about it. That is the part worth keeping.
Why it is worth fixing rather than living with
The escape is documented and autonomous, so the cost is not the retry — it is
what the retry teaches. PXX_ALLOW_FULL_SUITE=1 in front of everything is a
one-token habit, and an agent that acquires it stops reading the denial. The
owner asked for this guard after repeated incidents; a guard routed around by
reflex is not guarding.
CLAUDE.md is explicit that the answer is never to reshape the command to slip past — so the honest options are to fix the rule or to keep paying, and paying is what conditions the reflex.
The precedent is in the same file
Rule 2c carries exactly this fix and says why: matched at a COMMAND position
only, because "grep -n "testmgr.py --pin" CLAUDE.md is reading about the
rule, not running it, and refusing that is pure noise — the first thing this
rule did on the day it landed." Rule 3 was written without it.
Shape of a fix — not decided
Anchoring the loop keyword to a command position ((^|[;&|(]|&&|\|\|) as 2c
does, dropping the bare [[:space:]] alternative) kills both false positives,
because a python for and a prose for are both mid-line. It also weakens the
rule against a genuine cmd && for f in test/*.pas, which the anchor set would
still catch via &&.
Whoever takes it wants a POSITIVE CONTROL from the real population: the
for f in $(grep -rl TMethod test/*.pas) compile sweep that was correctly
denied that same session must still be denied. A narrowed guard that stops
catching the thing it was written for is worse than the noise.
2026-09-05 — third instance, and it is a DIFFERENT RULE with the same defect
Rule 3 (the shell-loop rule) was the original subject. The same failure lives in
the make test* rule, and it fired on a git commit whose MESSAGE named a make
target while describing one:
REFUSED: a full regression suite.
Nothing was run. The heredoc wrote a ticket and a commit message; the matched text was prose about a target, in the past tense, inside quotes.
So this is not one rule needing an anchor — it is the file's matching STRATEGY
applied to text that is not a command. Rule 2c already recognises the class
and was fixed for it ("reading about the rule, not running it"). Rule 3 and the
make test* rule both still read prose as invocation, and the population that
trips them is precisely the sessions writing tickets about testing, i.e. the
ones most likely to reach for the escape reflexively.
Three instances today, one session, two distinct rules. Each time the escape was used and justified rather than the command rephrased — which is the behaviour the file asks for and also the behaviour that erodes it, since a guard whose escape becomes habitual is a guard nobody consults.
The fix stays what this ticket already proposed — anchor at a command position, as rule 2c does — and now applies to more than rule 3. A positive control must keep a genuine full-suite invocation denied.