Background auto-mode sessions execute Bash calls matching ask rules (and PreToolUse hook ask decisions) without any prompt (2.1.215)
Summary
In a background session (sessionKind=bg, permission mode auto imposed by the background-job harness — no defaultMode set in any settings file), Bash tool calls that match user-scope ask permission rules execute with no prompt shown to the user — including calls where a PreToolUse hook additionally returned permissionDecision: "ask", and including an unsandboxed call.
This contradicts both:
permissions.md: "Hook decisions don't bypass permission rules. Claude Code evaluates deny and ask rules regardless of what a PreToolUse hook returns: … a matching ask rule still prompts even when the hook returned"allow"or"ask"."- Changelog 2.1.211: "Fixed auto mode overriding a PreToolUse hook's
askdecision for unsandboxed Bash — a hookasknow floors the decision at a prompt."
Environment
- Claude Code 2.1.215, macOS (Darwin 25.5.0)
- Session launched as a background job (transcript records
sessionKind: "bg",permission-mode: auto); user was actively chatting in the session while the calls ran - User-scope
~/.claude/settings.jsoncarriesaskrules includingBash(ssh *),Bash(gh pr merge *)(verified in force at test time; no broaderallowfor these in any scope; nodefaultModekey in any settings file) - A
PreToolUsehook (matcherBash) that classifies remote ssh commands and emits{"hookSpecificOutput":{"permissionDecision":"ask", ...}}for non-provably-read-only ones
Repro / observed timeline (from the session transcript JSONL)
Baseline: ordinary Bash calls in this session complete in ~2s.
| tool_use → tool_result gap | command shape | hook returned | matching rule | sandbox |
|---|---|---|---|---|
| 69.9s | gh pr ready … && gh pr merge … | (no decision) | ask: Bash(gh pr merge *) | disabled |
| 5.1s | ssh nas true && echo … | ask | ask: Bash(ssh *) | on |
| 59.5s | ssh nas true | allow | ask: Bash(ssh *) | on |
| 17.7s | ssh nas "true && true" | ask | ask: Bash(ssh *) | on |
| 29.6s | ssh nas "true && true" | ask | ask: Bash(ssh *) | disabled |
All five calls executed successfully with zero permission prompts rendered — the user was watching the screen for the last three specifically to count prompts and reports zero. For the 5.1s row the user was away from the machine entirely, so no approval click was possible; the command still ran 5.1s after issue.
The transcript contains the hook's hook_success attachments proving the "ask" decisions were emitted and received, and no permission-request/approval records between any tool_use and its tool_result.
Expected
Each of these calls should have blocked on a visible prompt (ask rule match; hook ask; per 2.1.211 at minimum the unsandboxed hook-ask case).
Notes / hypothesis
The anomalous 17–70s latencies (vs ~2s baseline) look like a server-side deliberation resolving the ask internally — possibly the auto-mode classifier answering prompts that should have gone to the human. If ask-rule matches are intended to be classifier-resolved rather than human-prompted in auto mode, the docs above don't say so — and that behavior makes ask rules unusable as a human-confirmation boundary for anything running in background/auto sessions (our use case: always-confirm rules on infrastructure-mutating commands).
Happy to provide redacted transcript excerpts.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Additional datum, opposite direction, same doc section: in an attended foreground session on the same machine/version (2.1.215), a Bash call matching the same user-scope
askrule (Bash(ssh *)) for which a PreToolUse hook returnedpermissionDecision: "allow"executed with no prompt (~8s turn, none of the 17–70s deliberation latency seen in the background case). That contradicts permissions.md's "a matching ask rule still prompts even when the hook returned 'allow'" — so as observed, hookallowoverrides an ask rule in attended sessions, while in background sessions ask rules resolve with no human regardless of hook decision. Either the doc paragraph doesn't describe current behavior, or the behavior is unintended in both directions.This is a significant gap. The behavior you're describing -- ask rules being bypassed silently in background auto-mode -- means the permission model is inconsistently enforced depending on whether a session was started interactively or as a background job. That's the opposite of what you'd want for unattended fleet runs: the more autonomous the session, the stricter the enforcement should be.
The WeatheredScientist observation in the second comment makes this more interesting: hook
allowoverrides anaskrule in attended sessions, while in background sessions ask rules resolve with no human regardless of hook decision. So the two bugs are mirror images -- one direction silences enforcement when it should prompt, the other direction bypasses the hook when it should enforce.A minimal fix expectation from a user perspective: background auto-mode sessions should log every ask-rule match to the transcript (they apparently do, given the 17-70s deliberation latency you mention), and they should either (a) pause and emit a notification to the waiting human, or (b) default to deny rather than proceed silently. "Silent proceed" is the least defensible outcome.
Two questions that would help scope the fix:
sessionKind=bgset explicitly via CLI args, or only via the background harness you're using?Disclosure: I'm working on Claudeverse, a fleet orchestration layer for Claude Code. Permission enforcement reliability in background sessions is one of the core gaps it addresses. This issue is directly relevant.
**Third datum, new session kind: the same silent ask-resolution occurs in an *attended
claude-desktop session — no background job involved.*
Environment: Claude Code 2.1.219 (desktop-embedded,
CLAUDE_CODE_ENTRYPOINT=claude-desktop,macOS Darwin 25.5.0).
CLAUDE_JOB_DIRunset; nodefaultMode,autoMode, orsandboxkeyin any settings file; same user-scope
askrules as the original report, includingBash(gh pr merge *). The user was in the session, watching specifically to count prompts.Probe (deliberately non-destructive — a PR number that cannot resolve):
gh pr merge 99999 -R <owner>/<private-repo> --merge
Timeline from the session transcript JSONL:
| t | event |
|---|---|
| 03:22:33.072Z |
tool_useissued || 03:22:33.124Z (+52 ms) |
hook_successattachment: PreToolUse hook fired and emittedpermissionDecision: "ask"|| 03:22:45.137Z (+12.1 s) |
tool_result:GraphQL: Could not resolve to a PullRequest with the number of 99999.— the call reached the GitHub API |Zero permission prompts rendered (observed directly). The ~12 s tool_use→tool_result gap
against a ~2 s session baseline matches the deliberation-latency signature from the original
background-session report (17–70 s there), i.e. the ask appears to be answered internally
rather than surfaced.
So both the matching
askrule and the hook'saskdecision failed to produce a promptin an attended, foreground-equivalent session — this is no longer scoped to background
jobs. Possibly relevant: the desktop session runs with
CLAUDE_CODE_CHILD_SESSION=1, so ifthe desktop app hosts sessions behind a parent harness, that harness may be resolving asks
the same way the background-job harness does (inference, not measured).
Combined with the earlier findings (background: ask executes silently; subagent tool calls:
hook ask auto-resolves, per our testing on 2.1.220), the pattern as measured on this machine
is now: a PreToolUse hook's
askdecision and matchingaskrules produce a renderedprompt only in interactive terminal CLI sessions. Every other execution context we've
tested resolves the ask internally and runs the command. If that's intended, the docs
should say so; if not, the attended-desktop case seems like the highest-severity instance,
since desktop users have every reason to believe they're in the "attended" path the
changelog and docs describe.
Happy to provide redacted transcript excerpts for the desktop case as well.