[BUG] "Always allow" persists $VAR references as the literal __TRACKED_VAR__ token, producing rules that can never match
Summary
When a Bash command references a shell variable, approving it with "Yes, don't ask again" persists the variable reference into settings.local.json as the literal string __TRACKED_VAR__. The resulting rule can never match a real command, so the prompt returns on every invocation — and each approval appends another dead rule.
This is the unbounded-growth pattern #15116 describes as growing "until it causes crashes," with a different token. __NEW_LINE__ leakage is acknowledged (#15406 and #15116, both closed as duplicate; #15742, closed not_planned), and #31523 names the category "token injection." I can find no report of __TRACKED_VAR__.
Reproduction
In a scratch directory:
SP="/tmp/demo" && ls "$SP/somefile"
Approve with "Yes, don't ask again", then:
grep -c __TRACKED_VAR__ .claude/settings.local.json
Non-zero reproduces it. Run the identical command again — you are prompted again.
Real-world sample
This command:
cd /e/ClaudeCode/BlenderVideoPipeline && SP="C:/Users/Michael/AppData/Local/Temp/claude/E--ClaudeCode-Soundthread/e2d5d625-55fe-48f3-ab48-3d708bfe656b/scratchpad" && .venv/Scripts/python -m pipeline.audio.cdp render "$SP/demo.thd" --in "$SP/demo_in.wav" --out "$SP/demo_out.wav" && .venv/Scripts/python -c "import wave,sys; w=wave.open(sys.argv[1]); print('OUTPUT ok: frames=%d rate=%d dur=%.2fs'%(w.getnframes(),w.getframerate(),w.getnframes()/w.getframerate()))" "$SP/demo_out.wav"
persisted as two rules:
Bash(.venv/Scripts/python -m pipeline.audio.cdp render __TRACKED_VAR__/demo.thd --in __TRACKED_VAR__/demo_in.wav --out __TRACKED_VAR__/demo_out.wav)
Bash(.venv/Scripts/python -c 'import wave,sys; ...' __TRACKED_VAR__/demo_out.wav)
$SP became __TRACKED_VAR__, and the surrounding quoting is dropped ("$SP/demo.thd" became __TRACKED_VAR__/demo.thd).
Not command-specific
15 unmatchable rules across three unrelated repositories and four toolchains. Beyond the Python rules above, from a different repo:
Bash(curl -sL https://raw.githubusercontent.com/remotion-dev/remotion/main/packages/skills/skills/remotion/rules/__TRACKED_VAR__.md)
Bash(awk -v 'RS= ' '{print}' __TRACKED_VAR__.txt)
curl and awk. The leak is in the generalisation path, not in any one command's argument parsing.
Counter-evidence
The serializer is not uniformly broken. On the same machine, on the same day, the rule-saver rewrote a settings.local.json cleanly — adding an entry while preserving a $comment block, with no doubled backslashes and no sentinel. The defect is on the specific path that handles variable references.
Suggested fix
When generalising a command for persistence, emit a wildcard (*) where a value varies — never an internal __-delimited sentinel. And validate before writing: reject any candidate rule containing such a token. That is the validation gap #15742 enumerated before it was closed unfixed.
Environment
| | |
|---|---|
| Claude Code | 2.1.205 |
| OS | Windows 11 Pro 10.0.26200 |
| Shell | Git Bash (CLAUDE_CODE_SHELL=bash) |
| Mode | permissions.defaultMode: "auto" |