Repository navigation
Subagents can infinitely recurse via Task tool — no max depth limit #18100
Description
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
- Use
claude-opus-4.6(GitHub Copilot) as the model - In a session, trigger a complex investigation that causes the primary agent to dispatch an
exploresubagent with a detailed prompt (e.g. "very thorough exploration of [repo] to answer [complex question]") - The
exploresubagent, instead of using its own Read/Grep/Glob tools directly, dispatches anotherexploresubagent via Task with nearly the same prompt - 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:
- Receives prompt
- Outputs: "I'll launch a very thorough exploration..."
- Dispatches another explore subagent with the same prompt via Task
- Child gets aborted or repeats
Root Cause
Two contributing factors:
-
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.
-
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_depthparameter 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 +stepslimit as a safety netsteps: 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.6via GitHub Copilot - Platform: Windows 11 (pwsh 7)
Activity
github-actions commentedon Mar 18, 2026
This issue might be a duplicate of existing issues. Please check:
hasTaskPermissionguard intask.tsand why it can be bypassed.doomsday616 commentedon Mar 18, 2026
Thanks for flagging the potential duplicates.
Key differences from #17722 / #17721:
Different trigger condition: My case uses the default
"*": "allow"wildcard — I do NOT have an explicit"task": "allow"in my global permissions. ThehasTaskPermissionbypass described in docs: warn about infinite subagent recursion when permission.task is configured globally #17721 should not apply here, yet infinite recursion still occurred. This suggests there's a second code path that enables subagent nesting beyond the one identified in docs: warn about infinite subagent recursion when permission.task is configured globally #17721.Real-world data: This includes a concrete reproduction with 47 sessions, 20 levels of nesting depth, and a full session tree dump from SQLite — not a theoretical risk analysis.
Actionable request: [FEATURE]: Document infinite subagent recursion risk when permission.task is set globally #17722 asks for docs warnings. This issue requests a framework-level
max_depthguard on the Task tool as defense-in-depth, plus includes a tested workaround config usingpermission.task+steps+ anti-recursion prompts.Happy to close if maintainers consider this covered, but the different trigger condition (default wildcard vs explicit task permission) may indicate a separate bug.
tim-mohrbach-ikigai commentedon May 4, 2026
Adding a second reproduction case that hits the same framework gap via a different trigger path.
Additional trigger:
tools: task: truein agent frontmatterYour 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:
The
normalize()function inconfig/agent.tssilently translatestools: { task: true }intopermission: { task: "allow" }. ThecanTaskcheck intask.tsthen evaluates totrue:Because
canTaskistrue, thetasktool is not stripped from the child session:So the child inherits
task: allowand can spawn again. Every subagent withtools: task: truein 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.github-actions commentedon Jul 4, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
JoppeDC commentedon Jul 15, 2026
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-solviagithub-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
explorere-delegating to identicalexplorechildren instead of using its ownread/grep tools).
Two observations that I think justify a built-in backstop (maxDepth or a
cross-session budget) rather than docs alone:
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.
stepsresets per spawned session,and doom-loop detection is single-session, while the recursion happens across
sessions.
rekram1-node commentedon Jul 15, 2026
285d315