Bash permissions.deny rules not enforced in subagent (Agent/Task tool) shells — denied command executes, only post-hoc security warning
Summary
A permissions.deny Bash rule that is correctly enforced in the main conversation loop is not enforced inside a subagent spawned via the Agent (Task) tool. The subagent executed the denied command successfully; the only trace was a post-hoc SECURITY WARNING in the parent's task-notification saying the command "matches the user's configured deny rule … this command should not have executed".
Environment
- Claude Code 2.1.214 (CLI), macOS (darwin 25.5.0)
- Project
.claude/settings.json:
"permissions": {
"deny": [
"Bash(git stash)",
"Bash(git stash:*)",
"Bash(rtk git stash)",
"Bash(rtk git stash:*)"
]
}
What happened
- Main loop: a Bash call containing
rtk git stash listwas denied as expected ("Permission to use Bash with command … has been denied."). Enforcement works there — including the compound-command case. - A background subagent (Agent tool,
subagent_type: general-purpose, modelsonnet) working in the same project ran:
````
git stash push -u -m "wip: issue-1505 checked-op fix + tests"
The command executed (the stash was created and later consumed; work was recoverable, no data loss in this instance).
- The parent session's task-completion notification carried:
> SECURITY WARNING: This subagent performed actions that may violate security policy. Reason: [User Deny Rules] The subagent ran git stash push -u -m "wip: …", which matches the user's configured deny rule Bash(git stash:*) — this command should not have executed…
So a detection layer recognizes the violation post-hoc, but the PreToolUse permission gate that blocks the command in the main loop did not fire (or did not deny) in the subagent context.
Expected
Deny rules are a security boundary; they should bind every Bash execution in the session, including subagent (Task/Agent) contexts — same as the main loop. Post-hoc warning is detection, not prevention: for destructive commands (git reset --hard, rm, …) the damage is done by the time the parent is told.
Repro sketch
- Add
"Bash(git stash:*)"topermissions.denyin project.claude/settings.json. - Confirm the main loop refuses
git stash list. - Spawn a Task/Agent subagent whose work plausibly leads it to stash (e.g. "commit your work; keep the tree clean"), or that is directly told to run
git stash push. - Observe the subagent executes it; the parent later receives the SECURITY WARNING in the task notification.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Duplicate sweep before/after filing: closest existing reports are #54898 (per-agent permission scoping feature request, not deny enforcement) and #11226 (deny bypass via Edit/Write on hook files — same theme, different tool surface). Neither covers Bash deny rules not binding subagent shells, so this report stands alone.
Tried to reproduce this on 2.1.233 (Linux, fresh install, project
.claude/settings.jsonwith"deny": ["Bash(touch /tmp/denied-*)", "Bash(git stash)", "Bash(git stash:*)"]):touch /tmp/denied-main→ denied ✅touch /tmp/denied-sub→ denied inside the subagent ("Permission to use Bash with command touch /tmp/denied-sub has been denied"), file never created ✅git stash→ denied, no stash created ✅cd /tmp && touch /tmp/denied-…→ also denied ✅So on the current release,
permissions.denyBash rules are enforced in subagent shells the same as in the main conversation — I couldn't reproduce what you saw on 2.1.214. This appears to have been resolved in a recent release (changelog).Closing — if you still see this on the latest version, please reply to reopen and include the subagent's exact Bash command string from its transcript (a differently phrased command, e.g. wrapped in
sh -cor prefixed with other commands, is the detail we'd need).🤖 Generated with Claude Code
We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected? This issue will be closed automatically if there's no activity within 7 days.