Auto mode classifier blocks tool calls while session is in bypassPermissions
Environment
- Claude Code 2.1.223 (also observed on 2.1.221)
- Native install, macOS (Darwin 25.x), Apple Silicon
permissions.defaultMode: "auto"in user settings; sessions switched tobypassPermissionsinteractively- No managed policy settings present;
disableBypassPermissionsModenot set - No
autoModesection in user settings — shipped default rules throughout
Summary
In sessions running in bypassPermissions, tool calls are intermittently refused by the auto mode classifier with toolDenialKind: "automode-blocked".
The permission-modes documentation describes bypassPermissions as skipping permission checks. If that is accurate, the classifier should not be consulted in this mode at all, and this is a bug. If instead the classifier is retained in bypassPermissions by design — the way PreToolUse hooks are — then the behavior is correct but undocumented, and the documentation should say so, because the current wording leads operators to conclude the mode is broken.
Either way the operator-visible outcome is the same: a mode chosen precisely to stop interruptions still interrupts.
Evidence
Across a 50-session window, 6 sessions ran in bypassPermissions. Four automode-blocked denials occurred inside those sessions. In each case:
- The denial entry is main-loop, not a subagent (
isSidechain: false), so this is not a subagent inheriting a different mode. - The most recent
permissionModevalue recorded before the denial — also main-loop — isbypassPermissions. - The session continues emitting
bypassPermissionsafterwards, so the mode had not been switched away and back.
One session's mode/denial sequence, with consecutive identical events collapsed:
bypassPermissions x76 -> DENIED: automode-blocked -> bypassPermissions x42
A second session shows the same shape: 46 consecutive bypassPermissions events, then automode-blocked.
The blocked calls were ordinary read-only operations, including gh pr view <n> --json number,mergeStateStatus,reviewDecision. That command matches the shipped Read-Only Operations allow rule, so the block is also hard to attribute to any specific deny rule.
Steps to reproduce
Not reliably reproducible on demand — the intermittency is part of the report. Observed conditions:
- Set
permissions.defaultModeto"auto"in~/.claude/settings.json. - Start a session, switch it to
bypassPermissions. - Run a long session with many tool calls, including
/modelswitches and/compact. - Intermittently a tool call is refused with
automode-blockedwhile the mode is stillbypassPermissions.
Denial records live in the session transcript JSONL: user entries carrying a top-level toolDenialKind, interleaved with entries carrying permissionMode.
Expected
In bypassPermissions, no permission check runs and no classifier is consulted; a tool call executes or fails on its own merits.
Alternatively, the documentation states plainly that the auto mode classifier and PreToolUse hooks both still apply in bypassPermissions.
Actual
The classifier runs and can refuse the call, producing a denial the operator explicitly opted out of.
Impact
This is the difference between an agent that can run a multi-hour unattended task and one that cannot. Long-horizon autonomous work is the use case bypassPermissions exists for, and an intermittent block halts the run at an arbitrary point, often on a trivially safe read.
Documentation suggestions (minor, separable)
skipDangerousModePermissionPrompt: truereads as though it forces bypass mode. It suppresses the confirmation dialog only. One clarifying line in the settings reference would prevent the misreading.
- Hooks override
bypassPermissions, and the permissions page does not say so. APreToolUsehook returningpermissionDecision: "ask"prompts even inbypassPermissions. That is defensible — it lets a project keep a hard safety net the operator cannot switch off by accident — but it is the first thing an operator suspects when bypass "does not work", and the remedy (reading thepermission_modefield inside the hook and honoring it) is discoverable only from the hooks reference. Suggest cross-linkingpermission_modefrom the permission-modes section.
- Denial messages do not name the rule that fired. A refusal reads "Blocked by classifier" with no rule name, and its remedy line suggests adding a Bash permission rule even when the denied call was an
Edit. Naming the matched rule — e.g. "Self-Modification" — would tell the operator what consent is missing. As it stands, the only way to find out is to runclaude auto-mode configand match the action against the rules by hand. For thesoft_denyfamily this matters a lot: those rules clear when the user names the specific change, so a named rule is directly actionable.