Forked skill invoked from a subagent returns no task_id or output path, and its completion notification routes to the parent session

Status Closed — not planned
Maintainer reply None cached
Activity 1 comment · opened Aug 26, 2026 · closed Aug 27, 2026

Summary

When a subagent invokes a skill that runs forked (Skill({skill: "code-review", args: "max <pr>"})), it has no supported way to receive that skill's result. The tool result carries neither a task_id nor an output path, and the child's completion <task-notification> is routed to the parent session rather than to the subagent that made the call. The caller is left holding only a name.

This breaks the retrieval contract that TaskOutput's own deprecation notice states:

Background tasks return their output file path in the tool result, and you receive a <task-notification> with the same path when the task completes.

A forked skill honors neither half of that, and a subagent caller can't fall back to the notification either, because the notification goes somewhere it can't see.

What the caller actually receives

Skill "code-review" launched (forked execution, running in the background).

Running in the background as @code-review

That's the entire result. No task_id, no path.

Why it's a problem

The subagent had been instructed to run the review and write its findings to a file. With no in-band channel it did the only thing left: polled ListAgents on a ladder of blocking python3 -c "time.sleep(...)" calls, then located the child's task_id by listing the tasks/ directory on disk and parsed the child's agent-<id>.jsonl transcript to recover the findings.

That recovery path is one TaskOutput explicitly warns against:

For local_agent tasks: use the Agent tool result directly. Do NOT Read the .output file — it is a symlink to the full subagent conversation transcript (JSONL) and will overflow your context window.

So the documented guidance is "use the Agent tool result," but a forked skill never produces one for its caller. It worked here, but only by luck of a small transcript, and it cost ~20 minutes of wall clock — the poll ladder overshot the child's actual completion by 7 minutes sitting inside a sleep(480).

SendMessage({to: "code-review", ...}) does appear to work as a workaround, since the forked agent is named and reachable via ListAgents. But nothing in the Skill tool result suggests it, and it isn't obviously the intended retrieval mechanism for a skill result.

Reproduction

  1. From a Workflow script (or any Agent dispatch), spawn a subagent whose prompt tells it to invoke a skill that forks — the built-in code-review at max effort does — and to write the result to a file.
  2. Observe the subagent's tool result: a name only.
  3. Observe the child's completion notification arriving in the parent session's context, not the subagent's.

The fork is identifiable on disk by the agent-<id>.forked-skill.json / .forked-skill.marker.json marker files.

What would fix it

Open to whichever of these fits the architecture best:

  • Return a task_id and output path in the forked-skill tool result, matching every other background task type.
  • Route the completion notification to the calling agent when the caller is itself a subagent (or deliver it to both).
  • Give Skill() a way to request blocking/inline execution, so a caller that needs the result in-band can say so up front rather than discovering after the fact that it won't get one.
  • Failing all of the above: document SendMessage as the supported way to retrieve a forked skill's result, and say so in the Skill tool result itself.

Any one of the first three would remove the need for transcript parsing.

Environment

  • Claude Code v2.1.246
  • macOS (Darwin 25.5.0), arm64
  • Caller was a Workflow-spawned subagent; forked skill was the built-in code-review at max effort

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗