[Bug] Plugin API: a plugin's own hooks are skipped for $.agent.spawn made after its hook returned (auto mode then refuses the spawn)
Summary
A plugin's own hooks are skipped for an $.agent.spawn it makes after the hook that started the work has returned. In auto mode the spawn is then refused, because the plugin's own tool.check approval never runs.
Environment
- Claude Code 2.1.295 (2.1.296 installed alongside)
- Windows 11, desktop app
- Permission mode:
auto
Steps to reproduce
- In a mod, register an agent type with
$.agent.register(...). - Add a
tool.checkhook on{ tool: 'Agent' }that answers{ decision: 'allow' }for the mod's own agent types, and anagent.spawnhook that rewritescwd. - From a
tool.callorcommand.runhook, start a$.clock.after(0, ...)timer and return. - In the timer callback, call
$.agent.spawn({ subagentType: '<mod>:<type>', prompt, description }).
Actual
- The engine raises
tool.checkfor the Agent tool withnext.origin.plugin === '<mod>'. A second plugin'stool.checkhook sees it. - The mod's own
tool.checkhook does not run: not its body, and not its.catchhandler. - The mod's own
agent.spawnhook does not run either. automode refuses the spawn: "The server-side auto mode classifier gave no verdict for Agent: the request that produced this action did not ask for one."
The same spawn made inside the live hook, before it returns, runs both hooks: the approval and the cwd rewrite take effect, and the agent starts.
Expected
The $.agent.spawn doc says it "runs every hook but the calling one". The calling hook has already returned, and the plugin's other hooks (tool.check, agent.spawn) are not the calling one, so they should run. reference.md says work that outlives a dispatch is kept going with $.clock.after and $.clock.every, which is what the mod does.
Evidence
A trace in the mod recorded which path each of its own hooks took for each spawn:
live hook: check=body spawnHook=body -> {"model":"claude-haiku-5-5","agentId":"..."}
timer: check=none spawnHook=none -> {"deny":"The server-side auto mode classifier gave no verdict ..."}
A second plugin's tool.check hook logged the timer spawns with origin set to the first mod and an engine verdict of ask, at the same timestamps. So the event was raised, and only the calling plugin's own hooks were left out.
Related, same build
- The
cwdpassed to$.agent.spawnis dropped: anagent.spawnhook sees nocwd, and the agent runs in the session's folder. Rewritingcwdin anagent.spawnhook works. - An
Agent(<plugin>:<type>)allow rule in settings does not match a plugin's spawn; the engine's verdict staysask.
Workaround
Spawns are queued and made only inside a live hook of the mod: its pane and band renders, its agents' tool calls and turn ends, its commands. It works, but it needs a surface drawing or an agent running, and making a spawn from a render hook is a side effect where none belongs.