[FEATURE] Allow hooks to spawn subagents / background agents (programmatic dispatch from hook events)
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Hooks are Claude Code's mechanism for deterministic automation in the agentic loop — they fire on events (PreToolUse, PostToolUse, UserPromptSubmit, Stop, SubagentStop, SessionStart, FileChanged, etc.) and can inspect, block, or annotate what the main agent is doing. What they fundamentally cannot do today is initiate new agent work.
The official docs state the boundary plainly:
Command hooks communicate through stdout, stderr, and exit codes only. They cannot trigger / commands or tool calls.
So a hook can return additionalContext, a permission decision, or a block — but it cannot dispatch a subagent (the Agent/Task tool is Claude's to call, not a hook's) and it cannot launch a background agent. The only way a subagent or background agent gets spawned is if the main model spontaneously decides to do so in-context. That makes one of the most valuable automation patterns — "when X happens deterministically, go do Y in the background" — impossible to express reliably.
Concretely, none of these are achievable with hooks today:
- On
FileChangedto*.tf/*.sql/package.json→ spawn a security-review or dependency-audit subagent automatically, every time, regardless of whether the main model thinks to. - On
Stop→ dispatch a detached background agent to run the full test/lint/build suite and open a PR, without holding the interactive session open for the duration. - On
SubagentStop→ fan out N verification subagents that adversarially check the just-finished subagent's output. - On
SessionStart→ kick off a long-running background indexing/warm-up agent that's ready by the time the user's first real task lands.
The current workarounds all have real downsides:
- Rely on the model to decide — non-deterministic, undiscoverable, and exactly what hooks exist to replace.
- Shell out from a hook (
claude -p "..."headless, or anohup ... &process) — this escapes the session entirely: the spawned work doesn't appear in the agent view, shares no session context (session id, cwd semantics, permission mode, MCP connections), isn't lifecycle-managed by the supervisor, and has no first-class channel to report results back into the parent conversation. - Hooks block the critical path — they run synchronously with timeouts, so a hook can't itself become the long-running work; it can only gate it.
Proposed Solution
Add a first-class, opt-in way for a hook to request that Claude Code dispatch an agent, returned through the existing hook JSON output contract rather than by shelling out. Two granularities, ideally both:
1. Spawn an in-session subagent (lightweight, context-sharing, result returns to the parent loop):
{
"hookSpecificOutput": {
"hookEventName": "FileChanged",
"spawnSubagent": {
"agentType": "security-review",
"prompt": "Audit the just-changed Terraform for IAM over-permissioning.",
"blocking": false
}
}
}
2. Dispatch a detached background agent (heavyweight, independent session, visible in the agent view):
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"dispatchBackgroundAgent": {
"prompt": "Run the full CI suite; if green, open a PR. If red, summarize failures.",
"cwd": ".",
"label": "post-stop-ci"
}
}
}
Design questions worth settling as part of the request:
- Blocking vs. fire-and-forget —
blocking: truelets a hook synchronously gather a subagent's result before returning a decision (e.g. aPreToolUsehook that spins up a reviewer and denies the tool call if the reviewer objects);blocking: falsedecouples long work from the interactive loop. - Context propagation — spawned agents should inherit session id, cwd, permission mode, and MCP connections by default, with explicit overrides — precisely what the shell-out workaround can't do.
- Observability & lifecycle — dispatched background agents should appear in the agent view /
claude agents, be supervisor-managed (idle stop, restart on update), and auto-isolate file edits to a git worktree like other background agents. - Result routing — a clear path for a spawned agent's output/errors to surface back into the parent conversation (or a hook-specified sink).
- Guardrails — recursion/fan-out limits (a hook spawning agents whose hooks spawn agents…), per-session dispatch caps, and respect for the existing restriction that plugin subagent definitions can't carry
hooks.
Alternative Solutions
- Let the main model decide to delegate. This is the status quo; it's non-deterministic and undiscoverable — the opposite of what hooks are for.
- Shell out to
claude -p/ background processes from a command hook. Works mechanically but escapes session context, observability, supervisor lifecycle, and result routing (detailed above). It also fights the synchronous-with-timeout nature of hooks. - Claude Agent SDK orchestration out-of-band. Powerful, but requires building and running a separate application; it doesn't help users who want event-driven dispatch inside an interactive Claude Code session.
- Structured in-context orchestration (see the related #64767, "Support structured orchestration as a first-class agent behaviour"). That request makes the orchestrating model declare decomposition/parallelism/collation, and it explicitly treats hook-based orchestration as an inferior alternative because routing would be "invisible." This request is the complement, not a duplicate: some dispatch decisions should be deterministic and event-driven (run on every matching file change, every session stop) rather than left to model judgment. Both can coexist — the model orchestrates when judgment is needed; hooks dispatch when determinism is needed.
Priority
Medium - Would be very helpful
Feature Category
Hooks
Use Case Example
A platform team wants a hard guarantee that every change to infrastructure-as-a-code or auth-related files gets an independent security review — not "whenever the model remembers to." Today they can write a PostToolUse/FileChanged hook that detects the change and prints a reminder, but the reminder only nudges the main model; the review only happens if the model chooses to act on it.
With hook-initiated dispatch, the same hook returns spawnSubagent with their security-review agent type and the changed file's path. The review now runs deterministically on every qualifying change, with full session context, visible in the agent view, and its verdict routed back into the conversation — turning "we hope it gets reviewed" into "it is always reviewed," which is exactly the determinism guarantee hooks are meant to provide.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗