context:fork skill executions inherit the caller's StructuredOutput tool; forks ending on it return a generic success stub and their output is silently lost

Open 💬 0 comments Opened Jul 8, 2026 by yasyf

Summary

When a skill with context: fork frontmatter is invoked via the Skill tool from a subagent that has a StructuredOutput tool (e.g. any Workflow agent(..., {schema}) wrapper), the caller's StructuredOutput tool leaks into the forked execution's tool inventory. If the fork finishes by calling that leaked tool (which sets toolEndsTurn: true), the fork ends with no trailing assistant text, and the Skill relay — which appears to harvest only trailing text — returns a generic success stub to the caller:

Skill "<name>" completed (forked execution).

Result:
Skill execution completed

The fork's real answer is silently discarded. No error is surfaced anywhere: the workflow journal records a schema-valid (empty/fallback) result, the Skill tool result reads as success, and the fork's transcript shows minutes of genuine work ending in a StructuredOutput call whose payload is never delivered.

Environment

  • Claude Code 2.1.204, macOS (darwin 25.5.0)
  • Skill: a plugin skill with context: fork (observed with a codex-CLI wrapper skill, but nothing about it is skill-specific)

Reproduction shape

  1. Define a plugin skill with context: fork.
  2. Run a Workflow whose script calls agent(prompt, {schema: SOME_SCHEMA}) — the wrapper subagent gets a StructuredOutput tool to return its result.
  3. The wrapper's prompt tells it to invoke Skill(<the forked skill>) with a full self-contained question in args.
  4. The forked execution runs normally (its transcript shows real tool use for minutes), but — seeing StructuredOutput in its inventory — often "returns" its final answer by calling it instead of emitting plain text.
  5. The wrapper receives the stub above instead of the answer.

Evidence from one production run

A review workflow fired 18 Skill(<forked skill>) invocations from schema-bound wrapper agents (5 finder wrappers + 6 refuter wrappers, some retrying):

  • 18/18 forks spawned successfully and ran 1–6 minutes each (transcripts on disk, spawnDepth: 2).
  • 15/18 forks ended their turn on a tool_result for StructuredOutput (toolEndsTurn: true) with no assistant text after it → all 15 callers received the byte-identical 78-char stub.
  • 3/18 forks ended with plain assistant text → all 3 callers received the real answer. (Two of those had used ReportFindings, which does not end the turn, so they kept talking afterward.)
  • The correlation is 100%. No rate-limit/5xx/API errors anywhere in the window; failures and successes interleaved at the same concurrency, so this is not load-related.
  • One discarded payload was a complete, correct refutation verdict — the fork called StructuredOutput with it 2.3s before the wrapper received the empty stub. The wrapper then submitted a fallback "unavailable" verdict, silently degrading the review.

Expected behavior

Either (ideally both):

  1. Tool isolation: a context: fork execution should not inherit the calling agent's StructuredOutput (or other caller-scoped return tools) — the fork has no legitimate way to use it, and its presence actively invites the fork to "return" through it.
  2. Relay robustness: if a fork ends its turn on a tool call with no trailing text, the Skill relay should either deliver the last tool payload or return an explicit error — anything but a generic success stub that masks the data loss.

Workaround

Don't nest forked skills inside schema-bound subagents: we now ship the skill's recipe as a plugin agent definition and spawn it directly (agentType/subagent_type), which removes the inner fork entirely.

View original on GitHub ↗