Skip to content

[Bug] Claude CLI backend serializes all fresh sessions sharing one workspace via resolveCliRunQueueKey #91946

Description

@wangwllu

Summary

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)
  • macOS 25.4.0, Node v26.0.0

The offending code

dist/helpers-DmEWhF5F.jsresolveCliRunQueueKey:

function resolveCliRunQueueKey(params) {
  if (params.serialize === false) return `${params.backendId}:${params.runId}`;
  if (isClaudeCliProvider(params.backendId)) {
    const sessionId = params.cliSessionId?.trim();
    if (sessionId) return `${params.backendId}:session:${sessionId}`;
    const workspaceDir = params.workspaceDir.trim();
    if (workspaceDir) return `${params.backendId}:workspace:${workspaceDir}`;
  }
  return params.backendId;
}

For a fresh claude-cli session:

  • serialize defaults true (no override)
  • 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.

Impact (broader than #91940)

Any flow that creates multiple fresh claude-cli sessions in the same workspace hits this:

  1. sessions_spawn subagent fan-out (the [Bug] sessions_spawn children execute serially despite subagents.maxConcurrent=12 (v2026.6.5) #91940 symptom).
  2. Any orchestration that does multiple fresh claude-cli tool-backend invocations in the same workspace concurrently.
  3. 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)

  1. 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)) {
      const sessionId = 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.

  2. 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.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions