[CRITICAL] Subagent spawning and subagent pattern bugs trigger infinite recursion, infinite token usage, grossly inefficient token usage, and lost accumulated subagent work.
Multiple regressions compound into a catastrophic token burn scenario. Subagents recursively spawn child agents 50+ levels deep, ignoring CLAUDE_CODE_FORK_SUBAGENT=0. Permission denials trigger further agent spawning instead of stopping. Agents fetch individual files from GitHub repos via HTTP (one WebFetch or curl per file, each with a full prompt and context payload) instead of cloning locally. Subagent permissions do not propagate to the user for approval. And if the user interrupts any of this, all intermediate work from every agent in the tree is lost. The entire token spend goes in the garbage.
In the observed case: 1.2M+ tokens consumed in ~30 minutes on a task that should have been git clone + find . -name '*.sol'. The recursive agent tree was still growing when observed.
In another instance, this same pathological behavior consumed an entire Pro Max 20x plan 5-hour session token limit in under 5 minutes (4 million tokens consumed).
This is a major regression that shipped on or around June 10th.
Impact
This behavior has repeatedly consumed 5-hour session limits in minutes. Over 50% of a weekly Max x20 plan limit has been burned by errant subagent recursive spawning. Most recently, a 5-hour session window was consumed in under 5 minutes of subagent work, pushing the weekly limit to 80% utilization with 5 full days until reset.
The out-of-control recursive subagents also trigger rate limits. Subagents in the spawn tree then trigger immediate retries, hammering token limits even faster for entirely wasted work. On top of that, the top-level agent receives notifications with vague summaries and retries, causing it to question results and relaunch new agents — compounding the burn further.
<img width="578" height="102" alt="Image" src="https://github.com/user-attachments/assets/82b2ab9b-2a87-4cfc-83fb-1a411a495e62" />
Environment
- Claude Code v2.1.177, macOS, CLI
- Model: Opus 4.6 (1M context)++
- Plan: Pro Max x20
Bugs
1. CLAUDE_CODE_FORK_SUBAGENT=0 is not enforced
Set in ~/.claude/settings.json, confirmed present in the process environment:
{
"env": {
"CLAUDE_CODE_FORK_SUBAGENT": "0"
}
}
Completely ignored. Subagents spawn child agents without restriction. 50+ levels of recursion observed (see screenshot below). The agent tree nests so deep that labels truncate off the display.
Where are the flags to opt out of this behavior? Why are previous flags not being respected? What is the stable surface that users can expect to build on?
2. Permission denial triggers recursive agent spawning instead of stopping
When a subagent hits a permission denial (e.g., Bash command denied), the harness returns a message that explicitly says "STOP and explain to the user what you were trying to do and why you need this permission." The agent ignores this and instead spawns a child agent to work around the denial. That child hits the same wall and spawns another. This repeats indefinitely.
The permission denial is functioning as a recursive agent launch trigger.
3. Subagent permissions do not propagate to the user
Permissions requested by subagents should bubble up to the user for approval. They do not. This has been broken for months. Some permissions occasionally propagate and some don't, but in the observed case, zero permission prompts reached the user while agents repeatedly hit permission walls and spawned children to circumvent them.
If permissions propagated correctly, the user would have seen the first denial prompt, approved or denied it once, and the entire recursive chain would never have happened.
4. Agents fetch individual repo files via HTTP instead of cloning
This is a behavioral regression. Agents used to clone repositories locally and read them. They now fetch individual files from GitHub via HTTP, one file at a time. Each fetch is a separate tool call (WebFetch with full URL and prompt, or curl via Bash) carrying the entire conversation context. For a repository with hundreds of files, this means hundreds of round-trips, each re-sending the full system prompt and context at Opus pricing.
A git clone to /tmp followed by local file reads would accomplish the same thing in a single Bash call with no per-file token overhead.
5. All intermediate work is lost on interruption
If the user interrupts a running agent (the only way to stop a runaway recursive chain), all intermediate results from every subagent in the tree are discarded. The root agent receives nothing. Every token spent by every completed subagent is wasted.
In this case: 1.2M+ tokens with zero recoverable output. The user's only options are:
- Let it run and watch it burn the session budget on a recursive loop that will never succeed
- Kill it and lose everything, including legitimate work completed by early agents before the recursion started
There is no selective cancellation (kill a subtree, keep the root), no intermediate result recovery, and no way to steer a running agent.
6. Rate limit hammering from retry storms
When recursive subagents hit API rate limits, they trigger immediate retries. Subagents throughout the spawn tree all retry concurrently, consuming token limits even faster with entirely wasted work. The top-level agent receives vague summary notifications from these retries, causing it to question results and launch yet more agents — a positive feedback loop on top of the recursion.
The compound effect
These bugs are not independent. They chain:
- User asks agent to research a GitHub repo
- Agent tries to fetch files via HTTP instead of cloning (bug 4)
- Agent hits a permission wall on Bash
- Permission doesn't propagate to user for approval (bug 3)
- Agent spawns a child to work around the denial (bug 2)
- Child is allowed to spawn despite FORK_SUBAGENT=0 (bug 1)
- Child hits the same wall, spawns another child
- 50+ levels of recursion, 1.2M+ tokens burned
- Rate limits hit, retries hammer the API further (bug 6)
- User's only option is to kill everything and lose all work (bug 5)
Screenshot
Recursive agent tree showing 50+ levels of nesting, labels truncating off display, 29+ minutes elapsed:
<img width="955" height="1001" alt="Image" src="https://github.com/user-attachments/assets/122483c4-eb59-42a9-90ac-5e1f787c0698" />
Expected behavior
CLAUDE_CODE_FORK_SUBAGENT=0should prevent subagents from spawning child agents- Permission denials in subagents should propagate to the user for approval, not trigger workaround attempts
- A subagent that cannot complete its task due to permission denial should return the failure to its parent, not spawn another agent
- Agents should clone external repositories locally instead of fetching individual files via HTTP
- Interrupting a runaway agent tree should preserve intermediate results from completed subagents
- Even without any flags, there should be a hard depth limit on recursive agent spawning
- Subagent retries on rate limits should back off, not hammer concurrently
- Plan limit resets and/or API refunds should be considered for users affected by this regression
31 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
The compound failure mode you've documented here is important: when permission denials trigger agent spawning instead of stopping, and intermediate results are lost on interruption, the user ends up with zero recoverable output from 1.2M+ tokens of work.
The root invariant violation is in #5 — subagents have no durable state handoff channel. Each subagent's output exists only in the parent's context window, which means any interruption (user-initiated or rate-limit-triggered) discards all intermediate work atomically. There's no checkpoint, no partial result file, nothing the root agent can interrogate after a kill.
I've been running external polling orchestrators against Claude Code sessions to work around this exact gap. The pattern that survives interruption: each subagent writes its result to a named file before returning (not after — the after-return path is exactly what gets lost). The orchestrator reads those files on resume rather than relying on the context chain. It's a workaround, not a fix, but it does give you recoverable output from a partially-completed fan-out.
For #1 (CLAUDE_CODE_FORK_SUBAGENT=0 not enforced): this setting should gate the entire Agent tool call chain, not just top-level dispatch. If the flag is in settings.json but subagents are still spawning children, the flag is being evaluated at the wrong layer. Worth checking whether it's being read at session start but not propagated to each spawned subagent's environment.
The interaction between #2 and #3 is the most dangerous part of the compound bug: permission denial -> recursive spawn -> denial again -> deeper spawn is only possible because permission prompts never bubble to the user. If #3 were fixed alone, the recursion would stop at the first denial. That's the dependency to highlight to the team — fixing #3 blocks the pathological case from #2, even before #2 is addressed directly.
The detail that stands out to me is that a permission denial appears to be acting as a spawn trigger rather than a stop condition.
That's not really a tuning issue. A denied action is one of the few signals that should terminate a branch immediately. If a denial causes the agent to create another agent that attempts a workaround, you've created a recursion path.
I'd want a few guardrails enforced in the harness rather than left to agent behavior:
The
CLAUDE_CODE_FORK_SUBAGENT=0part is concerning for the same reason. If that setting can be bypassed, then it's functioning more like guidance than an actual gate. Spawn limits need to be checked by the spawn path itself, not trusted to agent behavior.One thing I'm curious about: when the tree was growing, were most child agents being created after permission denials, or was the top-level agent repeatedly relaunching new subagents after receiving incomplete/vague summaries from existing ones? Those seem like two different feedback loops and probably need different fixes.
Good question, @hiwasham. Each subagent would hit the permission issue, then say "I'm hitting a bash permission issue. I should try to work around it by starting another agent." So, yes, the agents were recursively launching themselves ad-infinitum.
I think that's the core failure mode.
A permission denial should be a stop condition for that branch. Once the agent starts treating "I'm blocked" as "find another path," recursion becomes possible because every individual spawn can be justified as a reasonable next step.
That's also why I don't think this is primarily a prompting problem. Even with explicit instructions not to spawn more agents, the model can always decide that one additional subagent is the exception that will unblock the task.
The place I'd expect this to be fixed is in the harness:
Any one of those reduces the blast radius. Together they prevent the "blocked → spawn → blocked → spawn" loop regardless of what the model decides.
The durable-handoff issue that @kcarriedo mentioned feels separate but complementary. Even if recursion still happens occasionally, preserving completed subagent work would make interruption much less costly.
I have been facing this issue since the release of "Workflows" (amazing feature btw). My understanding is that IF agents have orchestration instincts baked into their system prompt - they treat themselves as coordinators and re-delegate the actual build to another same agent (in my case it was an agent that defined for my use. Eg: "onnx-agent" spwans its own "onnx-agent" and soo on), spawning main→solver→sub-solver chains. Burns tokens, loses control, and is why earlier ones died mid-"let me delegate."
To mitigate this issue for now (just a Bandaid), I toned down the system prompts of my agents to the very minimum, without any of the orchestration instincts. It works now - not as great as before. But works - no token wastage - no sub-sub-sub-Agents spawning.
Hope it will help you guys as well.
===========
So this chaining of agents one after the other or you can see that one agent does a handoff (task handoff) To a different agent to continue from the same exact point where the initial one left off - This is the exact pattern that I see in the "workflows" feature.
Fixing the issue in the sub agents should be fairly easy we just need to find that exact thing that is common between the sub agents and the workflows that is causing this chaining of agents or the handoff - And remove that BS from the Sub-agents.
@AR6420 this matches the pattern @loncharles described, and the
onnx-agent → onnx-agent → onnx-agentexample is a good illustration of the failure mode.I think the important distinction is that the orchestration capability itself is not necessarily the problem — the problem is that the system currently allows delegation decisions to recursively compound without a hard boundary.
The minimal-prompt workaround makes sense because it removes the trigger, but it also means giving up some of the capability that makes workflows useful. It solves the symptom by making the agent less autonomous.
The more robust place to handle this is the spawn path:
Then you can keep stronger orchestration behavior without allowing
agent → solver → sub-solver → sub-sub-solverto grow indefinitely.The interesting question is whether the same mechanism is responsible for both cases:
If those are the same spawn path, one guard could address both.
I also suffered from this problem on or about the same date. It burned over
$1700 until it emptied my account.
Please get back to me on this.
Patrick Roebuck
On Fri, Jun 19, 2026 at 7:57 AM hiwa sham @.***> wrote:
@silversurfer562 sorry that kind of spend spike is painful .
Just to be clear: I’m not Anthropic and I don’t have access to accounts, billing, or any ability to review or refund charges.
For anything involving the $1700 spend, the only path is to contact Anthropic support/billing directly. In a lot of runaway-usage cases like this, they’re the only ones who can actually review the session and decide whether any credit or adjustment is possible.
On the technical side, what you’re describing matches a known failure mode being discussed in this thread: recursive subagent spawning where each “blocked” step triggers another attempt instead of stopping. Once that loop starts, it can escalate very quickly because every individual step looks locally valid.
The main mitigation class for this isn’t prompt-level control, but hard limits outside the model behavior:
If you want, I can help you break down exactly where in your workflow it started escalating, so you can avoid hitting the same pattern again in future sessions.
Thanks for getting back with me. It's turned into a bigger chore than I
expected.
Patrick
On Fri, Jun 19, 2026 at 9:29 AM hiwa sham @.***> wrote:
Yeah — the cleanup after one of these is its own headache.
Separate from the billing side: so this can't bite you again, there's a small Claude Code hook that hard-caps subagent spawns + recursion depth at the spawn path — a runaway tree halts in seconds instead of draining the account. MIT, free, drops into .claude/settings.json: https://github.com/hiwasham/loopbreaker
If you'd rather not wire it in yourself, email me at hiwasham @ gmail and I'll walk you through getting it in place on your setup.
The safest thing is to change the general-purpose agent definition by creating a custom def and removing the agent tool from it's perms list. There's really no benefit in subagent spawning when the top-level agent can trigger as many concurrent parallel agents as it needs. There's nothing I've seen that benefited from the subagents launching agents, even when it worked properly. They just do redundant and overlapping work and waste tokens, cause rate limiting, and confuse the top-level agent when information is filtered through multiple rewrites when handing context back up the tree.
Just ask claude to make one of these agent defs for you and to make this change. You can tell it to find the prompt that matches what Anthropic uses off github. You'll be happier after you've done this. Clearly Anthropic has no plans to fix this issue and no problems with screwing their customers.
Sharing a user-side mitigation that stops the recursive subagent spawning (bugs #1 and #2) for custom subagent definitions (
.claude/agents/*.md). It doesn't fix the harness-levelCLAUDE_CODE_FORK_SUBAGENT=0enforcement gap — it just removes the agent's ability to recurse in the first place, so the flag becomes moot for these agents.Root cause I hit: a custom subagent kept narrating "Launching the X agent to…" and actually spawning a clone of itself. Three compounding causes, three fixes:
1. Hard block — remove the
Agenttool from the subagent's allowlist. If the agent has noAgent/Task-spawn tool, it physically cannot spawn a child. This is the real fix; the other two just reduce the urge.Note the allowlist has no
Agenttool. Withtools:specified, the agent only gets exactly those — no spawning surface.2. Directive at top of body — kill the orchestrator instinct.
3. De-mimic the
descriptionexample. Mydescriptionfield had an example that literally narrated spawning a clone:assistant: "Launching arc-rule-analyzer to pin the rule…". The model was copying that first-person "Launching X agent" line verbatim. Changed it toassistant: "I will pin the rule…".Root cause was #1 + #3: no tool restriction plus an example that narrated spawning a clone. Fix #2 is belt-and-suspenders.
This is a workaround, not a fix for the reported bugs — the harness should still enforce
FORK_SUBAGENT=0and a hard recursion depth cap regardless of per-agent config. But if you're bleeding tokens right now, locking down thetools:allowlist on your custom agents stops the bleed today.Adding measured data from one real multi-agent
/implementrun (8 tasks, lead + orchestrator + implementer/reviewer/PE subagents, Opus, pay-as-you-go API). Two findings that I haven't seen quantified this specific way; both are about coordinator cache-read cost, adjacent to the liveness threads here.1. Automatic
idle_notificationbroadcasts are a per-message, context-scaling costWhen an agent goes idle/available, the harness broadcasts an
idle_notificationteammate-message to other agents. Each such message lands as an inbound user-record on the recipient and opens a turn, which re-reads the recipient's entire accumulated context from cache (billedcache_readat ~$0.50/M on Opus 4.8). The payload itself is tiny (~220 tokens); the cost is the full-context re-read it triggers — so the cost of each notification scales with the coordinator's current history size (≈ O(N²) over a session).Measured on the lead (main session):
SendMessagetool call by a subagent → ~85 (77%) were automaticidle_notificationthe agents didn't choose to send.cache_readtokens ≈ $14.01 = 44% of the lead's entire cache-read cost for the run (run total ~$293 after correcting for the artifact in #2).These notifications carry no actionable value to a coordinator that already drives the workflow via explicit messages. Asks: don't broadcast
idle_notificationto long-context coordinators by default (or make it opt-in/role-configurable); coalesce/batch idle transitions; deliver liveness state out-of-band (queryable, not a conversation message that forces a re-read).Note the knock-on effect on the liveness threads (#29271 etc.): because resurrection makes direct messaging to a specific subagent unreliable (delivery cold-reloads a "dead" agent, liveness uncertain), designs are pushed to route everything through one always-on coordinator — the expensive relay pattern (
A→BbecomesA→lead→B, an extra re-read on the largest context). The idle/resurrection model thus both adds cost directly and blocks the cheaper direct-peer patterns.2. Resurrection cold-reload duplicates the transcript → naive cost tooling over-counts ~16×
When an agent is marked
Completedfrom its last message and later cold-reloaded ("resurrected"), the full prior transcript appears to be rewritten into a new file with the originalrequestIds (related: #44724's cache_read=0 on resume). In this run, 95.6% of all usage-bearing records were duplicates — 1,485 unique API calls recorded 33,405 times (147 subagent files / 180 MB for an 8-task run).Consequence: a naive per-file token/cost sum reports ~$4,600, but deduping by
requestIdgives the real ~$293 (matches the statusline final figure and the API bill). So unlike the "real 10-20× inflation" reports, this particular apparent inflation was a measurement artifact — important to separate when triaging cost reports. Fixes: consumers should dedup byrequestIdbefore summing; reducing on-disk transcript duplication would help observability (and the per-subagent aggregation in #48040).Reproduction / measurement
userrecords matchingteammate-message ... teammate_id="..."; among those, count{"type":"idle_notification",...}vs deliberate bodies; compare toSendMessagetool_use calls summed acrosssubagents/*.jsonl; attributecache_read_input_tokensof the assistant turn following each notification.requestId(ormessage.id) across main + allsubagents/*.jsonl; unique count vs raw count reveals the duplication factor.The O(N²) framing is the interesting part here. The expensive piece isn't the idle payload itself — it's that each liveness event can trigger more coordination work against the largest context in the tree.
This feels like the same class of problem as unbounded spawning: something that should be tracked as system state is being represented as model-visible conversation.
Liveness, depth, and resource limits are properties of the dispatcher/harness. They shouldn't require the model to interpret messages and maintain the accounting correctly. The model should receive the work result, not be responsible for enforcing the boundaries around the work.
Curious about the run details: was there any hard depth/agent-count cap in place, or was the relay pattern (A → lead → B) the only thing preventing the tree from expanding further?
@loncharles I've think you've misunderstood what this does. This environment variable is _off_ by default; enabling it allows agents to fork themselves (ie. a subagent context is launched with everything that's already in the parent context, rather than a blank slate). So it's expected that setting
CLAUDE_CODE_FORK_SUBAGENT=0has no impact on this issue.The limit for subagent recursion is supposed to be 5 deep, so the endless recursion _does_ look like a bug.
I haven't seen similar behaviour since recursive subagents launched, but in the past I have run into some issues where subagents mistook themselves for the primary agent and tried to delegate their tasks (even without having the Agent tool!) — in the end I traced it down to confusing advice I was giving them in
CLAUDE.mdand/or the prompt definitions for custom agents in.claude/agents/.Your issue seems to be with the built-in
general-purposeagent, so I'd recommend auditing yourCLAUDE.md— this file is read by all agents (the primary agent and subagents) and if it contains advice for orchestrating subagents this may be contributing to what you're seeing. Advice that you want to give to the primary agent alone is best placed in an output style — although there are other approaches which are more harness-agnostic, eg. you can have distinct skills for the primary agent and subagents.Either way, you want subagents to know that they are subagents; the most reliable way I've found to do this is to establish protocols for how agents prompt each other, so that every agent can identify who has prompted them: you, or the primary agent, or another subagent.
I think Claude Code could do a better job of informing agents of their session's context (the hierarchy of parent agents, if any) — this would make these prompts a bit less tricky to get right. I do think that they should have invested more in guardrails in this area before releasing recursive subagents, rather than relying on users to figure out how to mitigate the potential misalignments and scope creep which can result from a feature like this.
The recursion problem here points at a deeper gap: once you're running multi-agent flows, there's no external layer that knows which agents started, which are still running, and which hit a wall and spawned children to work around it. The session itself can't see its own tree clearly enough to catch this before it spirals.
A few patterns that have helped reduce runaway spawning in practice:
CLAUDE_CODE_FORK_SUBAGENTat the start of a system prompt and hard-fail if the var is 0 rather than trying to spawn anyway.The 1.2M token runaway you described (@loncharles) is exactly the failure mode that an out-of-process state tracker is meant to catch and interrupt. The session can't interrupt itself once the recursion starts.
Happy to share the specific pattern we use for external cycle tracking if that would be useful for repro or workaround purposes while this gets fixed upstream.
One harness-level invariant would make this easier to test:
A
permission_deniedevent should terminally close that branch unless the user grants permission. No descendant agent should be spawned with the same parent task plus denial lineage.The denial record should carry a stable id, parent agent id, requested tool, and blocked action summary. Spawn attempts can then check that lineage before dispatch. That catches the dangerous loop directly: blocked -> spawn child -> same blocked action -> spawn child.
Separately, cost rollups should dedupe resurrected or copied transcript records by provider request id/message id before reporting spend. Otherwise the real runaway and the transcript-duplication artifact get mixed together, which makes triage harder.
---
_Generated with ax._
FOUND THE SOLUTION: Explicitly scope subagent tools
<img width="2170" height="552" alt="Image" src="https://github.com/user-attachments/assets/27120f9b-9464-4960-8bc7-ef6b95810109" />
I've found a reliable way to stop the runaway recursion at the definition level.
We need to clearly define and restrict the tools available to Subagents, rather than letting them inherit all tools from the Parent (the main Claude Code Agent).
Specifically: <ins>Exclude the
Tasktool</ins> from subagents.The
Tasktool is what enables Claude Code to spawn new agents. If subagents inherit this tool, they retain the ability to spawn their own children when they hit blockers, which is exactly what drives the recursive fan-out described in this issue. By strictly omittingTaskfrom their allowed tools, you can enforce a hard stop on the recursive fan-out/spawning at depth 1.Turns out, enforcing strict tool-scoping best practices at the definition layer completely bypasses the harness bugs, since it doesn't depend on
CLAUDE_CODE_FORK_SUBAGENTbeing honored.Note: This is a mitigation, not a fix. The env flag should still be enforced and a hard depth cap would close it at the source.
The thread now has converging signal from several people hitting the same failure, so it's worth consolidating into one proposal before the dup-close bot trips (it already flagged #68430 / #68110 / #67636).
The two trigger paths people reported are the same spawn path:
onnx-agent → onnx-agent → …), the same chaining the Workflows feature uses.Both are one agent deciding "one more subagent is the reasonable next step." Prompt-level fixes can't close it, because the model can always justify the exception. @AR6420's minimal-prompt workaround proves this — it stops the recursion only by removing the capability.
The fix belongs in the harness spawn path, not in agent behavior. Four guards, each independently reducing blast radius:
CLAUDE_CODE_FORK_SUBAGENT=0enforced at the spawn path, propagated into every child env. Today it reads like guidance, not a gate — if it's set and children still spawn, it's being evaluated at the wrong layer.Complementary, separable (per @kcarriedo): durable subagent handoff. Each subagent writes its result to a named file before returning, so an interrupt doesn't atomically discard the whole tree's work. Doesn't stop recursion, but makes interruption recoverable instead of a total loss.
This is causing real money damage, not just inefficiency — @silversurfer562 reported ~$1700 burned to an emptied account, my own case was 1.2M+ tokens in 30 min. A depth limit + global cap + denial-as-terminal would have bounded every one of these.
The lost-accumulated-work problem is what drove the snapshot system in cortex-harness. Before any cycle starts, the harness captures every uncommitted file as a byte-perfect blob — so if a subagent cycle fails, hangs, or gets killed, a revert restores your latest known-good state rather than wiping back to HEAD and losing work from prior valid cycles.
The infinite recursion / runaway token problem is handled through a hard turn cap (500 turns safety ceiling on all cycles, 25-turn slices for test cycles with resume across slices), a dead-man timer that force-kills a subprocess if stdout goes silent for 20 minutes, and a
CYCLE_COMPLETE/CYCLE_PARTIAL/NEEDS_HUMAN_INPUTsignal contract that every cycle must emit — a cycle that emits nothing is treated as partial, not success, so it can't silently advance the queue.The harness is CLI-agnostic (Claude Code and OpenCode adapters today, open interface for others) so the orchestration safety layer isn't tied to any one CLI's subagent implementation. Might be worth separating "orchestration safety" from "which CLI runs the agent" conceptually in native implementations too.
Repo for reference: https://github.com/arnavranjan005/cortex-harness
This matches a pattern we ran into while building a polling runner that coordinates multiple Claude Code sessions for GTM work. A few things that helped:
The June 10 regression timing matches: we saw the same explosion of depth on tasks that previously self-terminated cleanly. Reproducing consistently with a simple "summarize all files in this GitHub repo" task on Pro Max tier.
+1 on the depth-cap feature request. A per-session hard ceiling (max_spawn_depth in settings.json) that errors loudly would have saved a lot of tokens here.
The compounding failure chain you've documented here is a real problem, and the regression landing around June 10 timing matches what others are seeing.
A few observations from running agent workflows in production that might be useful while this gets patched:
On the FORK_SUBAGENT flag not being honored: we hit the same thing. The flag gets set on the orchestrator process but child subagents inherit a different environment initialization path that doesn't pick it up. What actually worked for us was setting it explicitly in CLAUDE.md as a rule ("do not spawn subagents for this task") rather than relying on the env var propagating correctly.
On work lost on interruption: this is the one that hurts most in overnight runs. The pattern we've settled on is checkpointing work into append-only state files at each major step so a killed agent tree doesn't lose everything. Not a fix, but it bounds the blast radius.
On the token burn from HTTP-fetching individual files: if you're working with repos, adding an explicit early-step instruction to clone locally before doing any file reads tends to stop the one-by-one fetching pattern. Something like "your first tool call must be a Bash call to clone the repo" in the task prompt.
The permission wall triggering more spawning instead of stopping is the one I don't have a clean workaround for. That one needs a platform fix. The behavior of "I can't do X, so I'll spawn a child to try X instead" is clearly wrong.
Hope this helps while the fix is in progress.
Adding three things that haven't been said yet, building on @kcarriedo's "depth-cap at the harness level, not per-agent config" — which is exactly right, and here's why the per-agent path can't work:
1. Why
CLAUDE_CODE_FORK_SUBAGENT=0and per-agent settings are ignored at depth. Subagents don't inherit the parent's settings/hooks/memory — that's a separate, documented gap. So a value set in~/.claude/settings.jsonor aPreToolUsehook in the parent governs the first spawn, but each nested child starts without that context, and nothing re-applies the cap downward. That's the structural reason a depth limit has to live at the harness/orchestrator level (one process that sees the whole tree), not in config the children never read. It also means an operatorPreToolUseguard on theTasktool can refuse the first-level spawn but won't reliably reach 50-levels-deep — worth knowing before you trust a hook to contain this.2. The permission-denial→spawn trigger has a blunt operator-side mitigation: remove the denial. Since a denied permission is acting as a spawn trigger rather than a stop, the runaway is gated on denials happening. Pre-granting the specific permissions the task actually needs (so the child hits an allow, not a deny) removes the trigger for that run. It doesn't fix the bug, but it stops the compound loop from igniting while you're waiting on the real fix. Pair it with running the task in a fresh session with the model pinned, and kill fast — these reports show a 5-hour window gone in minutes, so detection + immediate
/exitis the real circuit breaker, not config.3. The "lost accumulated work on interrupt" is partly recoverable without a snapshot system. Before you rebuild anything: each subagent's real output is written to the session transcript as
toolUseResultin~/.claude/projects/<encoded-path>/<session-id>.jsonl, even when the tree is interrupted and the rolled-up summary is lost. You can pull the intermediate results back out:@arnavranjan005's pre-cycle snapshot is the better prevention; this is the post-hoc recovery when you didn't have one. Worth checking the transcript before treating the work as gone.
The core asks (enforce the depth cap, make a denial stop rather than spawn, propagate the cap to children) are all harness-side — agreed they should be consolidated into one proposal before the dup-bot trips.
To repeat:
CLAUDE_CODE_FORK_SUBAGENTis off by default, and it has nothing to do with whether subagents have access to theAgenttool anyway.CLAUDE_CODE_FORK_SUBAGENT plays a role in whether a subagent is launched along with the current conversation context or not and whether it is foregrounded or backgrounded. It is a flag that has been added, respected, not respected, and whose behavior has changed version to version and has direct bearing on this issue.
Confirmed with both:Thariq @trq212 and Adam @dmwlff on CC team.
Similar story with CLAUDE_CODE_DISABLE_BACKGROUND_TASKS. Behavior is inconsistent, changes between versions, and is rendered useless if you end up in one of Anthropic’s “experiments”. Also confirmed with the parties above.
Anthropic has been asked several times to ship a stable product surface and fully document and respect opt out for experiments and changing behavior. They’ve said they are working on it. Or disabled an offending experiment, only to turn it back on in the next version and go radio silent.These flags are directly relevant
This is a well-documented chain of failure modes that I have been tracking too.
The core problem you are describing -- where CLAUDE_CODE_FORK_SUBAGENT=0 is ignored and the spawn tree runs until it hits a wall, then all intermediate work is discarded -- is a consequence of running subagents inside a single session with no external isolation boundary. When the parent process owns both the spawn tree and the work accumulator, a runaway at any level takes everything down.
The way I have been thinking about this: subagent containment has to happen at the process level, not inside the agent's own decision-making. The agent already decided to ignore the config flag, so asking it to self-limit is circular. What actually works is a host-level supervisor that enforces per-subagent timeouts and budget caps externally, kills the process group on violation rather than just the direct child, and checkpoints completed work to a location outside the agent's own context before the tree is terminated.
The "rate-limit retry storms" item is a direct consequence of no external backoff: each subagent retries independently, with no coordination, because they have no shared view of the rate limit state.
Happy to share more about what a host-level harness for this looks like if useful -- building exactly this kind of orchestration layer.
This is a textbook case of why prompt-level recursion limits don't work. The subagent spawns children, those children spawn more, and each level ignores the parent's depth because the limit is a prompt instruction — not an execution boundary.
The fix that actually stops this: enforce a hard subagent depth limit at the PreToolUse hook. The hook runs in a separate process from the agent — it can't be overridden, context-compacted away, or ignored. When the hook detects a
spawnorTasktool call at depth > N, it blocks the call before the child agent is created.I've been building ThumbGate which does exactly this.
npx thumbgate initinstalls a PreToolUse hook across Claude Code, Cursor, Codex, and other agent CLIs. The specific rule for this pattern:Task/spawn/ subagent-creation tool callsThe same hook also catches the infinite-loop variant (repeated identical tool calls) by tracking tool-call hashes and blocking after N repetitions of the same call signature.
The key principle: prompt limits are documentation, not enforcement. If the model can choose to ignore its own depth constraint — and it clearly does, 50+ levels deep — it's not a limit. Enforcement must live outside the agent's context.
Running into this exact scenario. We built a small polling harness that dispatches Claude Code agent fleets (think 4-8 concurrent sessions coordinated via file-based handoffs), and the recursive spawn problem is the single most painful thing to operate around right now.
The bit that kills us is point 3 from your description -- permission denials triggering more spawns instead of surfacing the error. We have seen a sub-agent hit a write permission gate, interpret the block as "I need help, spawn a child to try differently", and the child does the same thing. By the time the orchestrator notices something is wrong, you have 12 agents in a waiting loop and the session limit is gone.
A depth counter on the Agent tool would close 80% of this. Even a hardcoded max-depth=3 default with an env override would let teams tune it without waiting for a product decision. The
CLAUDE_CODE_FORK_SUBAGENT=0flag not being respected is the critical regression here though -- that should be a hard stop, not advisory.One thing that helped us as a workaround: a PreToolUse hook that checks the current nesting level (we pass it via an env var set by the orchestrator) and returns a block result if depth > N. Hacky, but it stopped the runaway burn while this gets fixed.
This whole recursive spawning + permission denial triggering more agents instead of stopping is a serious hole. The fact that you can lose all intermediate work when you have to kill it makes it even worse.
MartinLoop was designed to prevent exactly this — hard depth/budget controls, proper state so work isn’t lost, and enforcement that actually works instead of relying on the model to behave.
Straight up one of the cleaner solutions I’ve seen for this class of problem. Repo: https://github.com/keesan12/martin-loop