Subagent tool calls: PreToolUse denials don't surface to the parent, and hook payload carries the parent's session_id

Open 💬 0 comments Opened Jul 11, 2026 by wshallwshall

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

  1. Configure a PreToolUse hook that returns a deny decision for some tool (e.g. Edit/Write to a given path).
  2. From a parent session, spawn one or more subagents (Task/Agent) that attempt that denied tool call.
  3. 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

  • Same "silent no-op" class as #4669 / #37210 (malformed hook output does nothing, no warning; closed as not planned).
  • Underlying docs gap: the documentation doesn't state whether hooks fire for subagent tool calls or what session_id they carry — I had to determine both empirically. Broader coordination context and the full write-up are in the companion feature request #76727.

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.

View original on GitHub ↗