fix(state-line-guard): Edit path used String.replace, not a function

current.replace(oldStr, newStr) with newStr as a STRING lets JS treat
$-sequences inside it ($&, $`, $', $$, $n) as special replacement
patterns, even though oldStr (the search side) is a plain string. A
new_string documenting old backtick-substitution style ($`cmd`) - the
kind of prose a STATE.md shell-conventions section writes routinely -
triggers it. Measured against the real bug (.claude/STATE.md,
2026-08-15): a 5-line addition on a 112-line file projected to 219
lines and was wrongly denied.

Fix: current.replace(oldStr, () => newStr) - a function replacement is
never pattern-substituted, covering every $-sequence at once. The
replace_all branch (split/join) was never affected.

Direction was always fail-closed (over-blocks, never under-blocks a
real oversize), but it made exactly the STATE.md files that document
shell conventions hard to edit via Edit.

state-line-guard-selftest.sh: 23/23 (+2, section 9: $` as the repro,
$& as a second sequence proving the fix is general).

Also updates CLAUDE.md's pinned selftest counts (197/178/69/21 were
already stale before this session's own additions; now 206/183/69/23).
This commit is contained in:
Kjell Tore Guttormsen 2026-08-15 20:37:18 +02:00
commit b5c860eb03
3 changed files with 104 additions and 6 deletions

View file

@ -114,7 +114,10 @@ if (toolName === 'Write') {
}
projected = toolInput.replace_all
? current.split(oldStr).join(newStr)
: current.replace(oldStr, newStr);
// A string replacement here would let JS interpret $-sequences inside
// newStr ($&, $`, $', $$, $n) as special patterns instead of literal
// text - a function replacement is never pattern-substituted.
: current.replace(oldStr, () => newStr);
currentLines = countLines(current);
}