[Bug] CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS is skipped when ultracode is active — a Workflow ran 7 agents under a cap of 3 (2.1.261)

Status Open
Reported on v2.1.261
Maintainer reply None cached
Activity 0 comments · opened Sep 5, 2026

Measured 2026-09-05 on one Linux host. Everything below is reproducible with the commands shown. Related but distinct: #80082 (docs omitted the cap) and #90483 (Workflow concurrency derives from CPU count) — this report is about the guard being skipped, not undocumented.

Summary

The subagent launch guard in Claude Code 2.1.261 returns early, without checking the concurrency cap, when the session is in ultracode. CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS therefore never gated the primary executor that ultracode uses (the Workflow tool's agents), and a session with the cap set to 3 ran seven subagents at once. The documentation states the exemption in one sentence; the impact — the cap does not apply to the one mode designed to fan out — is not stated.

Environment

| | |
|---|---|
| Claude Code | 2.1.261 (BUILD_TIME 2026-09-04T16:49:50Z, GIT_SHA 1349cf9c224caea8b7721f717418645e28ebacb8); binary ~/.local/share/claude/versions/2.1.261, sha256 prefix 4ae40dd1784e8575 |
| OS | Linux 6.8.0-90-generic, x86_64, 48 CPUs |
| Settings | ~/.claude/settings.json env.CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS = "3", CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH = "1", CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = "1" |
| Effort | /effort → "Set effort level to ultracode (this session only): xhigh + dynamic workflow orchestration" |

Observation 1 — the launch guard, from the binary

strings -n 8 ~/.local/share/claude/versions/2.1.261 \
  | grep -o 'tn=()=>{let as=N7n();if(t.taskRegistry.getConcurrentSubagents()<as)return;[^`]*`[^`]*`'

returns (one line, wrapped here):

tn=()=>{let as=N7n();
  if(t.taskRegistry.getConcurrentSubagents()<as)return;          // below the cap: allow
  if(I("tengu_amber_kestrel",!1))return;                           // feature flag: allow
  let Hi=t.getAppState();
  if(oA(t.rootToolSurface.mainLoopModel,Xa(Hi),Hi.ultracode))return;   // ULTRACODE: allow, cap not applied
  return p("subagent_launch","subagent_concurrency_cap"),
         rEe.of(t.session).recordRefused("concurrency_limit"),
         new cT(`Concurrent subagent limit reached. You can run ${as} subagents at once. Do not retry. If the user wants more concurrent subagents, ask them to increase CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS.`)}

and the predicate:

strings -n 8 ~/.local/share/claude/versions/2.1.261 | grep -o 'function oA([a-zA-Z,]*){[^}]\{0,300\}}' | head -1
→ function oA(e,t,o){return o===!0&&Lc()&&$w(e,t)==="xhigh"}

So with appState.ultracode === true (and effort resolving to xhigh), the guard returns before recordRefused; no Concurrent subagent limit reached error is ever produced. A second, unrelated early return is gated by the feature flag tengu_amber_kestrel.

Observation 2 — the documentation

https://code.claude.com/docs/en/sub-agents, fetched 2026-09-05: "By default, when 20 subagents are running in a session, spawning another with the Agent tool fails with Concurrent subagent limit reached … Sessions with ultracode active are exempt: the limit isn't enforced there." Also: "Resuming a subagent that already finished takes a fresh slot without checking the limit, so resumes can push the running count past it."

Observation 3 — a measured seven-wide session under a cap of 3

Session e5624aea-c8ec-42a5-a611-75680c14c0ba (~/.claude/projects/-home-noah-src-aprender/), 2026-09-05, ultracode on, cap 3 in settings. The prompt asked for a triage of 174 GitHub issues; the model ran one Workflow (workflows/wf_c7fc31c7-66a.json: agentCount: 16, 23 classify:* labels planned, subagents/workflows/wf_c7fc31c7-66a/journal.jsonl 16 started lines).

Peak overlap of the 16 agent transcripts, computed by a sweep line over each agent-*.jsonl's first and last timestamp (starts before ends at equal instants):

FAIL transcript-gate: 4 subagents overlap at 2026-09-05T08:21:20.624Z (cap 3) — agent-a38727447fceee0e0.jsonl started while 3 still ran
FAIL transcript-gate: 5 subagents overlap at 2026-09-05T08:21:20.624Z (cap 3) — agent-a7ffa13c902cfe9da.jsonl started while 4 still ran
FAIL transcript-gate: 6 subagents overlap at 2026-09-05T08:21:20.624Z (cap 3) — agent-a939676531c36c6b8.jsonl started while 5 still ran
FAIL transcript-gate: 7 subagents overlap at 2026-09-05T08:21:20.624Z (cap 3) — agent-ac9e6a26a356bce32.jsonl started while 6 still ran

(Tool: a sweep-line script over the agent-*.jsonl timestamps in the workflow's transcript directory; the same sweep over the flat subagents/ directory reports nothing, because Workflow agents are written under subagents/workflows/<run>/.) The Workflow runtime's own concurrency is min(16, availableParallelism() − 2) — 16 on this host — and that, not the env var, bounded the run.

Also measured in the same session family: the guard's message never appeared in any transcript; SubagentStart hook events fire for these agents with agent_type: "workflow-subagent".

Expected vs actual

  • Expected: with CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=3, no more than 3 subagents run at once, for any executor, or the documentation states plainly that ultracode is the exception and how to cap it.
  • Actual: under ultracode the cap is skipped in code; the Workflow tool runs up to 16 agents concurrently; the only mention is one sentence in the sub-agents page.

Impact

Operators who set the env var to bound cost and host load (and audit it after the fact) have no bound in the mode designed to fan out. A cap that exempts its heaviest producer is not a cap.

Workaround that works (measured)

A SubagentStart hook that counts live agents and exits 2 when the count would exceed the cap. SubagentStart fires for the Agent tool, for Workflow-spawned agents and for SendMessage resumes, carries agent_id, and exit 2 prevents the spawn. Measured: 28 Agent dispatches in one message under a hook cap of 2 → never more than 2 running, 11 refused before spawn, the rest ran as slots freed.

Suggested fixes (any one)

  1. Apply the cap to Workflow-spawned agents and resumes; drop the ultracode early return, or
  2. Make the exemption opt-in (CLAUDE_CODE_ULTRACODE_IGNORES_CONCURRENCY_CAP=1) and default to enforced, or
  3. Expose the Workflow runtime's concurrency (min(16, cpus−2)) as a setting and document that CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS does not bound it.

Whichever is chosen: state the behaviour in the CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS documentation itself, next to the variable.

View original on GitHub ↗