Security heuristics silently discard permissions.allow Bash rules — no warning, rule never appears in /permissions
Summary
Certain permissions.allow Bash rules are discarded at load. They remain in settings.local.json, never appear under /permissions → Allow, and are never honored at execution time.
There is no warning, no log line, and no UI indication that a rule was rejected.
Version: 2.1.233
Platform: macOS (Darwin 25.6.0)
Minimal reproduction
.claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(python3:*)",
"Bash(echo:*)",
"Bash(npm run:*)",
"Bash(npx tsc:*)",
"Bash(realpath:*)"
]
}
}
Start a fresh session (not --continue/--resume, so settings are re-read), then open /permissions → Allow.
Expected: all 5 rules listed.
Actual: 3 listed —
Bash(echo:*)
Bash(npx tsc:*)
Bash(realpath:*)
Bash(python3:*) and Bash(npm run:*) are absent.
Because all five rules use identical syntax and live in the same file, this isolates the behavior to the rule content rather than a syntax or file-loading problem.
Second, independent signal
In the same session, the per-call approval annotation distinguishes the two paths directly:
⏺ Bash(realpath)
⎿ /path/to/cwd <- no annotation: an allow rule matched
⏺ Bash(python3 -c "print(1)")
⎿ 1
⎿ Allowed by auto mode classifier <- no rule matched; the classifier approved it
With auto mode disabled, that second path prompts instead.
This annotation is a reliable per-command discriminator: a silent ⎿ means a permission rule matched, while Allowed by auto mode classifier means none did.
Impact
Observed in a repo whose entire allowlist lives in settings.local.json. Rules that appear configured are silently inert, so agent and subagent runs prompt for commands the user believes are already approved.
Why this is still a bug if the stripping is intentional
Refusing to blanket-allow patterns like python3:* is defensible on its own terms. The problem is that it happens silently:
- Claude Code itself writes these rules when the user selects "Yes, and don't ask again" on such a command.
- The rule is then discarded on the next load with no feedback.
- Users end up believing they have an allowlist that does not exist.
At minimum, a rejected rule should emit a warning at load and be visibly marked in /permissions. The CLI already surfaces validator warnings for other rule problems, e.g.:
Permission deny rule (.claude/settings.json): Write(<path>) is not matched by file
permission checks — only Edit(path) rules are. Use Edit(<path>) instead.
so the reporting channel already exists.
Related issues
- #34106 — "Security heuristics override explicit permission allowlist". Same mechanism, independent repros (
stat,python3). Closed as inactive and locked, not resolved; its closing message invites a new issue. - #18160 — open, same general class.
- #48762 — auto-closed as duplicate of #34106.
- #20449, #58652 — related allowlist-not-honored reports.
Filing fresh per the guidance on #34106 rather than commenting on a locked thread. The minimal repro and the classifier-annotation signal above are the parts I did not find in the earlier reports.
3 Comments
Corrected impact data. My original version of this comment counted only main-thread tool calls and omitted subagent transcripts, which turn out to be roughly 68% of all Bash activity. It claimed the previous model never invoked
python3. That was wrong. Corrected figures, same date window and same matching method on both sides:| Period | Model | Bash tool calls | of which
python3| Rate ||---|---|---|---|---|
| Jul 27–31 | Opus 4.8 | 410 | 27 | 6.6% |
| Aug 3–17 | Opus 5 | 3905 | 452 | 11.6% |
The rate roughly doubled, but the much larger change is absolute: 27 → 452 occurrences (~17×), driven mainly by total tool volume growing ~9.5× rather than by any change in how often the model reaches for an interpreter.
The point that remains, and the reason I filed: the rule was not honored in either period. Only the frequency of encountering the problem changed. As overall tool-call volume grows, a silently-discarded
Bash(python3:*)rule goes from a rare annoyance to a routine one, with no signal to the user that the rule they wrote is inert.Apologies for the noise on the first pass — I'd rather correct it than leave inflated numbers in a bug report.
This is auto mode working as documented rather than rules being discarded at load. On entering auto mode, broad allow rules that grant arbitrary code execution — wildcarded interpreters like
Bash(python3:*), package-manager run commands likeBash(npm run:*), blanketBash(*), andAgentrules — are set aside so the classifier reviews those commands; narrow rules carry over, and the set-aside rules come back when you leave auto mode: https://code.claude.com/docs/en/permission-modes#how-the-classifier-evaluates-actionsVerified on 2.1.234 with your exact
settings.local.json: in manual mode/permissions→ Allow lists all five rules; after switching to auto mode it lists onlyecho,npx tscandrealpath(matching your screenshot); Shift+Tab back out of auto mode and all five are listed and honored again. Your "Allowed by auto mode classifier" annotation confirms the session was in auto mode when you checked.If you want
python3to run unreviewed, use a mode other than auto for that session, or narrow the rule (e.g.Bash(python3 scripts/build.py *)). Closing as answered — reply if you see the rules missing in manual/acceptEdits mode and we'll reopen.🤖 Generated with Claude Code
---
_Generated by Claude Code_
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.