[BUG] workflow sub-agent concurrency dropped to 2 after 2.1.226 (still present in 2.1.234)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
SYMPTOM
Claude Code runs at most 2 workflow sub-agents in parallel on a 24-core host.
Controlled A/B, same host, same script, back to back — 6 agents each sleeping 20s:
2.1.226 all 6 started simultaneously peak 6 span 36.6s
2.1.233 three waves of two peak 2 span 92.1s
2.1.234 three waves of two peak 2 span 97.1s
On 2.1.226 the cap was never reached, so 6 is a floor. On 2.1.233+ agents start at
the exact millisecond a predecessor ends. 2.65x wall-clock penalty on multi-agent work.
CAUSE (from the shipped binaries)
The workflow scheduler's CPU-count source changed after 2.1.226. Immediately before
the "Workflow agent() call cap reached" string:
2.1.226: F2b( os.cpus().length )
2.1.233: UZv( os.availableParallelism() )
2.1.234: fBv( os.availableParallelism() )
"cpus().length" no longer appears anywhere in 2.1.233/2.1.234. Claude Code is a Bun
build whose bundled node:os shim defines availableParallelism() as
navigator.hardwareConcurrency. On this host stock Node 22 returns 24 for both APIs,
so the under-reporting comes from the Bun runtime, not the OS.
What Should Happen?
2.1.226 all 6 started simultaneously peak 6 span 36.6s
Error Messages/Logs
Steps to Reproduce
- Create conc.js:
export const meta = {
name: 'conc-probe',
description: 'Measure real workflow agent concurrency: 6 agents each sleeping 20s',
phases: [{ title: 'Probe', detail: '6 agents x sleep 20' }],
}
phase('Probe')
const out = await parallel([1,2,3,4,5,6].map(i => () =>
agent(Run exactly this one Bash command and nothing else: sleep 20 && echo probe${i}\n +
Then reply with exactly the single word: probe${i}\n +
Do not read any files. Do not run any other command. Do not explain.,
{ label: probe-${i}, phase: 'Probe', effort: 'low' })))
log(probe finished: ${out.filter(Boolean).length}/6)
return { returned: out.filter(Boolean).length }
- Create run.sh:
#!/bin/sh
Re-test workflow concurrency against any Claude Code build, WITHOUT restarting your session.
Usage: run.sh /path/to/claude (default: the staging build)
B="${1:-$GNAI_CLAUDE_PATH}"
echo "testing: $B ($("$B" --version 2>/dev/null))"
cd /tmp || exit 1
"$B" -p 'Call the Workflow tool exactly once with {"scriptPath": "conc.js"}. Wait for completion, then reply with only the returned JSON.' \
--output-format json --max-turns 10 --allowedTools "Workflow" "Bash" >/dev/null 2>&1
D=$(find "$HOME/.claude/projects/-tmp" -type d -name "wf_*" -printf "%T@ %p\n" 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
python3 - "$D" <<'PY'
import json,glob,os,sys,datetime
def par(s): return datetime.datetime.fromisoformat(s.replace("Z","+00:00"))
iv=[]
for p in glob.glob(os.path.join(sys.argv[1],"agent-*.jsonl")):
ts=[json.loads(l).get("timestamp") for l in open(p,errors="replace") if l.strip()]
ts=[t for t in ts if t]
if ts: iv.append((par(min(ts)),par(max(ts))))
evs=sorted([(s,1) for s,e in iv]+[(e,-1) for s,e in iv]); cur=peak=0
for _,x in evs: cur+=x; peak=max(peak,cur)
t0=min(s for s,_ in iv); span=(max(e for _,e in iv)-t0).total_seconds()
print("PEAK CONCURRENCY = %d over %d agents, span %.1fs (6 => ~30s PASS, 2 => ~95s FAIL)" % (peak,len(iv),span))
- Then run run.sh
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.226
Claude Code Version
2.1.234
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Xterm
Additional Information
_No response_