You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Claude CLI backend in dist/helpers-DmEWhF5F.js resolves its run queue key to claude-cli:workspace:<workspaceDir> for every fresh CLI session (i.e. one without a cliSessionId). All such runs are then funnelled through a shared KeyedAsyncQueue, which is strictly serial per key.
The result: any flow that fans out N fresh claude-cli sessions in the same workspace executes one-at-a-time, regardless of how high subagents.maxConcurrent (or any other lane concurrency) is configured. The lane is honored at dispatch — it just gets re-serialized at the backend layer.
This is the underlying root cause of #91940 (sessions_spawn children running serially); filing separately because the bug is more general than that one symptom and the #91940 OP is currently mis-pointed at the lane subsystem.
Version
OpenClaw 2026.6.5
claude-cli runtime, model anthropic/Claude-Opus-4.7-hq (routed to claude-cli)
cliSessionId is empty (no parent session to resume)
workspaceDir is inherited from the parent
→ key collapses to claude-cli:workspace:/Users/wanglu241/.openclaw/workspace for all such runs.
enqueueCliRun(queueKey, task) then routes them through a per-key KeyedAsyncQueue, which awaits each task before starting the next.
Behavioral contract this breaks
agents.defaults.subagents.maxConcurrent (and similarly the subagent lane setMax) imply that subagent throughput scales with that number. The lane does honor it — gateway logs show setMax lane=subagent requested=12 effective=12 and sessions_spawn correctly dispatches with lane: \"subagent\". But the actual CLI execution layer ignores the lane and re-serializes by workspace.
Net effect: the configured concurrency is silently invisible.
Reproduction
See #91940 — spawning 4 trivial children with sleep 4 in one main turn produces strictly serial CLI exec, ~12s gap between each child's cli exec log line, even with subagents.maxConcurrent=12. activeSessions rises to 5, confirming session objects are not the bottleneck — only enqueueCliRun is.
Any orchestration that does multiple fresh claude-cli tool-backend invocations in the same workspace concurrently.
Mixed cases where one workspace runs both interactive sessions and background fresh CLI runs — they will queue against each other.
Isolated cron runs do run concurrently because they take a different code path (a separate run/session/workspace shape), so users hitting this end up routing fan-out work through cron — at the cost of losing the sessions_spawn lifecycle (streamed completion events back to parent, sessions_yield, etc.).
Suggested fixes (any of these, in order of preference)
Use runId (or the child session key) as the queue key for fresh CLI runs, instead of falling back to workspace:
if(isClaudeCliProvider(params.backendId)){constsessionId=params.cliSessionId?.trim();if(sessionId)return`${params.backendId}:session:${sessionId}`;return`${params.backendId}:run:${params.runId}`;// each fresh run gets its own key}
This preserves serialization within a resumed session (which is presumably why session-id keying was added) while allowing fresh sessions to execute concurrently.
Allow sessions_spawn subtasks to safely set backend.serialize = false, so the existing escape hatch in resolveCliRunQueueKey is reachable from spawn callers without reaching into backend internals.
At minimum, document the actual semantics of agents.defaults.subagents.maxConcurrent and lane concurrency for the claude-cli backend — i.e., that same-workspace fresh CLI sessions will still serialize. The current schema and config UI strongly imply otherwise.
Notes
This bug is invisible to openclaw config get and gateway lanes-style introspection because the serialization happens below those layers. Any fix should consider exposing CLI-backend queue depth too.
I'd also suggest renaming the current resolveCliRunQueueKey workspace fallback to make its intent explicit (e.g. resumeWorkspaceSerialization), since the current name reads like a generic key resolver but it's actually the source of an unintuitive cross-run dependency.
Happy to test a candidate patch. Tracking the user-visible symptom in #91940.
Summary
The Claude CLI backend in
dist/helpers-DmEWhF5F.jsresolves its run queue key toclaude-cli:workspace:<workspaceDir>for every fresh CLI session (i.e. one without acliSessionId). All such runs are then funnelled through a sharedKeyedAsyncQueue, which is strictly serial per key.The result: any flow that fans out N fresh
claude-clisessions in the same workspace executes one-at-a-time, regardless of how highsubagents.maxConcurrent(or any other lane concurrency) is configured. The lane is honored at dispatch — it just gets re-serialized at the backend layer.This is the underlying root cause of #91940 (sessions_spawn children running serially); filing separately because the bug is more general than that one symptom and the #91940 OP is currently mis-pointed at the lane subsystem.
Version
2026.6.5anthropic/Claude-Opus-4.7-hq(routed toclaude-cli)The offending code
dist/helpers-DmEWhF5F.js—resolveCliRunQueueKey:For a fresh
claude-clisession:serializedefaults true (no override)cliSessionIdis empty (no parent session to resume)workspaceDiris inherited from the parent→ key collapses to
claude-cli:workspace:/Users/wanglu241/.openclaw/workspacefor all such runs.enqueueCliRun(queueKey, task)then routes them through a per-keyKeyedAsyncQueue, which awaits each task before starting the next.Behavioral contract this breaks
agents.defaults.subagents.maxConcurrent(and similarly thesubagentlane setMax) imply that subagent throughput scales with that number. The lane does honor it — gateway logs showsetMax lane=subagent requested=12 effective=12andsessions_spawncorrectly dispatches withlane: \"subagent\". But the actual CLI execution layer ignores the lane and re-serializes by workspace.Net effect: the configured concurrency is silently invisible.
Reproduction
See #91940 — spawning 4 trivial children with
sleep 4in one main turn produces strictly serial CLI exec, ~12s gap between each child'scli execlog line, even withsubagents.maxConcurrent=12.activeSessionsrises to 5, confirming session objects are not the bottleneck — onlyenqueueCliRunis.Impact (broader than #91940)
Any flow that creates multiple fresh
claude-clisessions in the same workspace hits this:sessions_spawnsubagent fan-out (the [Bug] sessions_spawn children execute serially despite subagents.maxConcurrent=12 (v2026.6.5) #91940 symptom).claude-clitool-backend invocations in the same workspace concurrently.Isolated cron runs do run concurrently because they take a different code path (a separate run/session/workspace shape), so users hitting this end up routing fan-out work through cron — at the cost of losing the
sessions_spawnlifecycle (streamed completion events back to parent, sessions_yield, etc.).Suggested fixes (any of these, in order of preference)
Use
runId(or the child session key) as the queue key for fresh CLI runs, instead of falling back toworkspace:This preserves serialization within a resumed session (which is presumably why session-id keying was added) while allowing fresh sessions to execute concurrently.
Allow
sessions_spawnsubtasks to safely setbackend.serialize = false, so the existing escape hatch inresolveCliRunQueueKeyis reachable from spawn callers without reaching into backend internals.At minimum, document the actual semantics of
agents.defaults.subagents.maxConcurrentand lane concurrency for theclaude-clibackend — i.e., that same-workspace fresh CLI sessions will still serialize. The current schema and config UI strongly imply otherwise.Notes
openclaw config getandgateway lanes-style introspection because the serialization happens below those layers. Any fix should consider exposing CLI-backend queue depth too.resolveCliRunQueueKeyworkspace fallback to make its intent explicit (e.g.resumeWorkspaceSerialization), since the current name reads like a generic key resolver but it's actually the source of an unintuitive cross-run dependency.Happy to test a candidate patch. Tracking the user-visible symptom in #91940.