Plugin API: a background agent from $.agent.spawn loses its report under acceptEdits (completion notification skipped as already-notified)

Status Open
Reported on v2.1.296
Maintainer reply None cached
Activity 0 comments · opened Oct 13, 2026

Environment: Claude Code 2.1.296, Windows 10, CLI (claude -p, plugin loaded with --plugin-dir).

Related: #98943 (SubagentHandback refused in bypassPermissions, report hidden), #101782 (plugin hooks skipped for $.agent.spawn).

Summary

A plugin starts a background subagent with $.agent.spawn, using a type it registered with $.agent.register. When the subagent finishes, the engine skips the normal completion notification to the parent. Then everything depends on the subagent calling SubagentHandback. In default and auto mode it does, and the report arrives. Under --permission-mode acceptEdits the subagent has no SubagentHandback tool, so the parent never sees the report, even though the work was done.

The built-in Agent tool with run_in_background: true under acceptEdits does not lose the report: it arrives as an ordinary task notification. The failure is specific to agents a plugin spawns.

Steps

  1. A plugin registers a type, $.agent.register({ name, description, prompt, model: "sonnet", effort: "low", tools: ["Read"] }), and a tool whose tool.call handler calls $.agent.spawn({ subagentType: "<plugin>:<name>", prompt, model }) and returns the agentId at once.
  2. Run claude -p --permission-mode acceptEdits --plugin-dir <plugin> and have the model call that tool with a task that reads a file containing a marker and returns it.
  3. Repeat in default mode and in auto mode.

Result

debug.log, all three modes, at the subagent's end:

[INFO] [Stall] agent_completion agentId=<id> agentType=<plugin>:<name> exitPath=completed
[DEBUG] [enqueueAgentNotification] skipped taskId=<id> status=completed taskPresent=true reason=already-notified

Nothing had notified the parent before this line. The parent's transcript has no notification for this agent at that point.

| Mode | SubagentHandback in the subagent's .prefix.json toolNames | Report reaches the parent |
|---|---|---|
| default | yes | yes, as an agent message from the handback |
| auto | yes | yes, as an agent message from the handback |
| acceptEdits | no (not deferred either) | no — the work is done and the report is silently lost |
| built-in Agent, background, acceptEdits | — | yes, as a task notification |

Reproduced in six separate acceptEdits runs.

Expected

A plugin-spawned background agent's completion reaches the parent the same way a built-in background agent's does, whatever the permission mode. Or SubagentHandback is offered to it in acceptEdits too.

Workaround in use

The plugin keeps the agentIds it spawned. A tool.call hook on SubagentHandback marks the ones that reported back. On turn.complete for one of its own agents that did not report, the plugin sends e.answer to the main loop with $.prompt.submit. That gives exactly one delivery in all three modes. But the plugin keeps that list in memory, so it is lost if the plugin reloads while the agent is still running, and every plugin that spawns agents has to repeat this logic.

View original on GitHub ↗