Skip to content

Subagents can infinitely recurse via Task tool — no max depth limit #18100

Closed as not planned

Description

@doomsday616

Bug Description

Subagents (explore, general) can invoke the Task tool to spawn child subagents, which in turn spawn their own children, with no recursion depth limit. This leads to runaway nesting that wastes tokens and creates dozens of useless intermediate sessions.

Reproduction

  1. Use claude-opus-4.6 (GitHub Copilot) as the model
  2. In a session, trigger a complex investigation that causes the primary agent to dispatch an explore subagent with a detailed prompt (e.g. "very thorough exploration of [repo] to answer [complex question]")
  3. The explore subagent, instead of using its own Read/Grep/Glob tools directly, dispatches another explore subagent via Task with nearly the same prompt
  4. That child does the same thing, creating an infinite recursion chain

Observed Behavior

In my session, this produced:

  • 47 total sessions (1 parent + 46 nested child/grandchild sessions)
  • 20 levels of nesting depth
  • 18 layers of pure explore→explore recursion (depth 2 through 19), each doing nothing except re-dispatching the same task to another explore agent
  • Only the deepest agent (depth 19) actually performed real work (grep, read files)

Session tree (abbreviated):

depth 0: "OpenCode的一个BUG" (build, primary) depth 1: 8 subagent sessions (general + explore) depth 2: "Search thinking_budget and reasoning stripping" (explore) depth 3: "Explore thinking/reasoning stripping" (explore) depth 4: "Thorough exploration of reasoning stripping" (explore) depth 5: "Explore reasoning stripping logic" (explore) ... (same pattern continues) depth 19: "Explore opencode reasoning stripping" (explore) ← finally does actual work here depth 20: 4x general subagents (read files)

Each intermediate explore agent (depth 2–18) follows the exact same pattern:

  1. Receives prompt
  2. Outputs: "I'll launch a very thorough exploration..."
  3. Dispatches another explore subagent with the same prompt via Task
  4. Child gets aborted or repeats

Root Cause

Two contributing factors:

  1. Model behavior: The LLM re-delegates its own task to an identical subagent instead of using its available tools directly. This is a known LLM tendency — the model interprets "thorough exploration" as requiring delegation rather than direct action.

  2. Framework gap: OpenCode has no recursion depth guard on the Task tool. A subagent at any depth can spawn further subagents without limit. There is no max_depth parameter or built-in circuit breaker.

Expected Behavior

OpenCode should enforce a maximum nesting depth for subagent Task invocations. Suggested default: 3 levels. When the limit is reached, the Task tool should either:

  • Return an error message telling the agent to perform the work directly
  • Or simply be unavailable (removed from the tool list)

Workaround

Until a framework-level fix is available, users can mitigate this via opencode.jsonc agent config:

jsonc "agent": { "general": { "prompt": "CRITICAL ANTI-RECURSION RULE: You are a subagent. NEVER use the Task tool to dispatch another subagent for the same task you were given. If you need to explore code, use Read/Grep/Glob/Bash directly. Only use Task to dispatch a DIFFERENT type of agent (e.g. general dispatching explore) for a genuinely independent subtask — never to re-delegate your own work. Maximum nesting depth is 1.", "steps": 30 }, "explore": { "prompt": "CRITICAL ANTI-RECURSION RULE: You are a subagent. NEVER use the Task tool to dispatch another subagent. You have direct access to Read, Grep, Glob, and Bash tools — use them yourself. Do NOT delegate your work to another agent.", "permission": { "task": { "*": "deny" } }, "steps": 20 } }

Key points:

  • explore: Deny Task permission entirely (it's a read-only agent that should never need to spawn children)
  • general: Keep Task permission but add anti-recursion prompt instructions + steps limit as a safety net
  • steps: Hard cap on iterations — even if the prompt instruction is ignored, it won't run forever

Environment

  • OpenCode v1.2.16
  • Model: claude-opus-4.6 via GitHub Copilot
  • Platform: Windows 11 (pwsh 7)

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 18, 2026
  2. github-actions commented on Mar 18, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  3. doomsday616 commented on Mar 18, 2026

    @doomsday616
    Author

    Thanks for flagging the potential duplicates.

    Key differences from #17722 / #17721:

    Happy to close if maintainers consider this covered, but the different trigger condition (default wildcard vs explicit task permission) may indicate a separate bug.

  4. assigned and unassigned on Apr 26, 2026
  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. tim-mohrbach-ikigai commented on May 4, 2026

    @tim-mohrbach-ikigai

    Adding a second reproduction case that hits the same framework gap via a different trigger path.

    Additional trigger: tools: task: true in agent frontmatter

    Your reproduction came from model behaviour — the LLM chose to re-delegate to a subagent rather than act directly. There is a second way to trigger the same infinite chain purely through config, without any unusual model behaviour:

    ---
    mode: subagent
    tools:
      task: true   # ← sufficient to trigger unbounded recursion
    ---

    The normalize() function in config/agent.ts silently translates tools: { task: true } into permission: { task: "allow" }. The canTask check in task.ts then evaluates to true:

    const canTask = next.permission.some((rule) => rule.permission === "task")

    Because canTask is true, the task tool is not stripped from the child session:

    ...(canTask ? [] : [{ permission: "task", pattern: "*", action: "deny" }]),

    So the child inherits task: allow and can spawn again. Every subagent with tools: task: true in its frontmatter is permanently vulnerable to this, regardless of model choice or prompt wording.

    Observed impact

    This configuration triggered a chain of 612 nested sessions over 73 minutes — roughly one new session every 7 seconds. The chain was not stopped by any OpenCode safeguard. It stopped only because the API credit limit was exhausted. Without that external constraint it would have continued indefinitely.

    Why this matters for the fix

    The existing related issue #17722 and its docs-only PR #17724 only warn about "task": "allow" in the global permission block. They don't cover the frontmatter path. A user who carefully avoids the global config trap can still hit this by writing a perfectly ordinary-looking agent definition.

    A runtime depth limit in task.ts (Option A from your proposed fixes) would close all three trigger paths — model behaviour, global config, and agent frontmatter — with a single code change.

  7. github-actions commented on Jul 4, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  8. JoppeDC commented on Jul 15, 2026

    @JoppeDC

    Adding a data point, because I hit this at a much larger scale than the 47 sessions
    described above — and I think it's worth reopening as more than a docs issue.

    Last week i prompted my agent to "Review the uncommitted changes" (v1.17.18, gpt-5.6-sol via
    github-copilot) which spawned a subagent that recursed via task:
    9,640 subagent sessions, 32 levels deep, peaking at ~2,100 new sessions per
    10 minutes. It only stopped when 7,652 sessions simultaneously hit HTTP 402 —
    it had drained my entire Copilot quota.
    A second incident the day before produced 6,810 sessions the same way (built-in
    explore re-delegating to identical explore children instead of using its own
    read/grep tools).

    Two observations that I think justify a built-in backstop (maxDepth or a
    cross-session budget) rather than docs alone:

    1. The failure is strongly model-dependent — the identical config with
      claude-sonnet-5 never recursed once across ~80 sessions. You can't test your
      way to safety per-model; the propensity shows up only when you switch models.
    2. None of the existing safeguards can see it: steps resets per spawned session,
      and doom-loop detection is single-session, while the recursion happens across
      sessions.
  9. rekram1-node commented on Jul 15, 2026

    @rekram1-node
    Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions