What happened
I use PreToolUse deny hooks to enforce a worktree-per-session policy. Building that enforcement, I hit two ways it silently fails for subagent (Task/Agent) tool calls. Both fail silent, not safe — the guard is off and nothing tells you.
Bug 1 — a subagent's permission denials don't surface to the parent.
With a PreToolUse deny in place, subagent tool calls were correctly blocked by the hook — but the parent session's result came back with permission_denials: [] (empty). A fan-out of subagents appears to succeed while having written nothing. There is no signal to the parent (or the user) that the guard fired inside the subagents.
Bug 2 — a subagent's hook payload carries the PARENT's session_id.
The JSON payload delivered to a hook invoked for a subagent's tool call reports the parent session's session_id, not a distinct one for the subagent. Any session-keyed lock or claim registry is therefore silently broken for subagents: N parallel subagents all present the same session_id and all pass the same mutex.
These compound: you can't build a reliable session-scoped guard (Bug 2), and even when a guard does fire in a subagent, the parent can't tell (Bug 1).
Steps to reproduce
- Configure a PreToolUse hook that returns a
deny decision for some tool (e.g. Edit/Write to a given path).
- From a parent session, spawn one or more subagents (Task/Agent) that attempt that denied tool call.
- Observe the subagents are blocked, then inspect the parent's result payload.
Expected
- Bug 1: the parent's result reflects that subagent tool calls were denied —
permission_denials is populated (or there is some equivalent signal), so a fan-out that wrote nothing does not read as success.
- Bug 2: the hook payload for a subagent's tool call carries an identifier that distinguishes the subagent from its parent, so session-keyed coordination works.
Actual
- Bug 1: parent result shows
permission_denials: []; the fan-out looks successful despite writing nothing.
- Bug 2: the subagent hook payload's
session_id equals the parent's.
Impact
Any hook-based enforcement (worktree isolation, path guards, claim/lock registries) that needs to cover subagent activity has a hole in it, and the hole is invisible at runtime. This is the single most time-consuming class of problem I hit building cross-session guards.
Related
Environment
- Solo dev, Windows 11, VS Code extension + Desktop app.
- Observed while running many concurrent sessions against one repo (~20 git worktrees, 15–20 sessions at a time).
- Caveat: n=1 — one machine, one repo. Reproduced consistently in my setup; offered as evidence, not a general claim.
What happened
I use PreToolUse
denyhooks to enforce a worktree-per-session policy. Building that enforcement, I hit two ways it silently fails for subagent (Task/Agent) tool calls. Both fail silent, not safe — the guard is off and nothing tells you.Bug 1 — a subagent's permission denials don't surface to the parent.
With a PreToolUse
denyin place, subagent tool calls were correctly blocked by the hook — but the parent session's result came back withpermission_denials: [](empty). A fan-out of subagents appears to succeed while having written nothing. There is no signal to the parent (or the user) that the guard fired inside the subagents.Bug 2 — a subagent's hook payload carries the PARENT's
session_id.The JSON payload delivered to a hook invoked for a subagent's tool call reports the parent session's
session_id, not a distinct one for the subagent. Any session-keyed lock or claim registry is therefore silently broken for subagents: N parallel subagents all present the samesession_idand all pass the same mutex.These compound: you can't build a reliable session-scoped guard (Bug 2), and even when a guard does fire in a subagent, the parent can't tell (Bug 1).
Steps to reproduce
denydecision for some tool (e.g. Edit/Write to a given path).Expected
permission_denialsis populated (or there is some equivalent signal), so a fan-out that wrote nothing does not read as success.Actual
permission_denials: []; the fan-out looks successful despite writing nothing.session_idequals the parent's.Impact
Any hook-based enforcement (worktree isolation, path guards, claim/lock registries) that needs to cover subagent activity has a hole in it, and the hole is invisible at runtime. This is the single most time-consuming class of problem I hit building cross-session guards.
Related
permissionDecision: "deny"ignored for Edit tool — tool executes despite deny #37210 (malformed hook output does nothing, no warning; closed as not planned).session_idthey carry — I had to determine both empirically. Broader coordination context and the full write-up are in the companion feature request Cross-session coordination for independently-launched Claude Code sessions #76727.Environment