Skip to content

v1.8.1 — fix recursive subagent loop

Choose a tag to compare

@muckybuzzwoo muckybuzzwoo released this 13 Jun 16:48
· 12 commits to main since this release

Critical bugfix: runaway recursive subagent loop. /yt-extract <url> (default and --full-transcript modes — any path that dispatches a subagent) could spawn an ever-deepening chain of general-purpose subagents (~one new level every ~30 s), burning tokens and never producing output. Each dispatched worker's first action was Skill(yt-extract) — it re-invoked this skill instead of running the Python command it was handed, and Step 1 then dispatched the next worker, ad infinitum.

Root cause

The recursion needed two things, both introduced in v1.6.0:

  • v1.6.0 removed disable-model-invocation: true to make the skill callable programmatically by other skills. That flag was also the hard structural guard that kept a subagent from reaching /yt-extract through the Skill tool. Its removal was compensated only by a tightened description — a soft, model-interpreted guard. But the worker's own task ("Extract all data for this YouTube video…") reads as an explicit extract request, so the soft guard matched exactly the case it was meant to exclude.
  • The skill dispatched subagent_type: general-purpose, which inherits all tools — including Skill and Agent. With the skill now model-invocable, the worker could (and did) re-invoke it.

Fixed

  • New restricted worker agent agents/extract-worker.mdtools allowlist is Bash, Read, Glob, Grep only, so the worker has no Skill and no Agent tool and physically cannot re-invoke the skill or spawn subagents.
  • SKILL.md Step 1 now dispatches subagent_type: "yt-extract:extract-worker" instead of general-purpose. The skill stays model-invocable (programmatic invocation by other skills is preserved).
  • Both Step 1 subagent prompts gained a "you are a LEAF worker — run only the Bash command, never invoke a skill or dispatch a subagent" guard (defense-in-depth).
  • Worker no longer asks the user directly. The subagent prompts used to instruct the worker to call AskUserQuestion for FOLDER_EXISTS and SCREENSHOTS_ASK_USER, but subagents have no AskUserQuestion tool (it depends on the main-chat UI). The worker now returns these states; the orchestrator (main context) asks the user and re-dispatches with --force / explicit timestamps. The --transcript-only path is unaffected.
  • New test tests/test_skill_contract.py — static contract test locking the dispatch target (restricted worker, not general-purpose), the worker tool allowlist (excludes Skill/Agent/Task), and that no worker prompt instructs AskUserQuestion. Closes the previously untested orchestration-layer gap.

Upgrade note

Adds a fourth plugin component type (an internal agent). No user-facing CLI change. To restore the hard block instead — at the cost of programmatic invocability — re-add disable-model-invocation: true to the SKILL.md frontmatter.