Skip to content

subagent spawn persists raw provider instead of CLI runtime — depth-2 cold spawns silently die with 'lost execution context' (two unpatched #57326 call sites, fix included) #92405

Description

@oiGaDio

Summary

sessions_spawn (subagent spawn) persists the raw canonical provider (anthropic) into the child session entry instead of resolving it to the configured CLI runtime (claude-cli). For depth-2 spawns with no cached CLI session binding, the child run registers, never launches a CLI process, and dies 30 minutes later when the sweeper reaps it:

"outcome": { "status": "error", "error": "subagent run lost active execution context" }
"endedReason": "subagent-error"

This is another unaddressed call-site of the regression family tracked in #57326 — the same missing resolveCliRuntimeExecutionProvider call that has now been fixed in the queued-followup path (#82857), cron executor, attempt-execution, and agent-runner.runtime. The two spawn dispatchers were never patched.

Both files verified still unpatched on current main (checked 2026-06-12) and in the released 2026.6.5 dist.

Environment

  • OpenClaw 2026.6.5 (5181e4f), Linux arm64
  • CLI-backed runtime: agents.defaults.models["anthropic/..."].agentRuntime.id = "claude-cli", auth profile anthropic:claude-cli (OAuth)
  • Agent models use canonical refs (anthropic/claude-...), per the documented config pattern

Live evidence on 2026.6.5 — even successful spawns report the unresolved provider

Every sessions_spawn ack on 2026.6.5 returns the raw provider:

{
  "status": "accepted",
  "resolvedModel": "anthropic/claude-...",
  "resolvedProvider": "anthropic",   // ← never "claude-cli", even when claude-cli runs it
  "modelApplied": true
}

Depth-1 spawns still work because agent-runner.runtime repairs the provider downstream. Depth-2 spawns hit the unrepaired window (below) and intermittently die.

Smoking gun — single-field diff between working and failing spawns

Same agent (planner), same model, same config, same depth=2, same day:

session persisted modelProvider cliSessionBindings result
run A claude-cli present ✅ ran 9.2 min, transcript 11 KB, done
run B anthropic absent ❌ silent 31.5 min, empty transcript, "lost execution context"
run C (auto-retry) anthropic absent ❌ identical

The only persisted difference is whether the provider got resolved. When a previous spawn left a cached CLI session binding, downstream reuse masks the bug; a cold spawn (no binding) launches nothing and is swept at +30 min (sweepStaleRunContexts, maxAgeMs=1800s). That cache-masking is why the failure looks intermittent and why it has been hard to pin down.

Root cause (current main, verified at source)

Call site 1 — src/agents/subagent-spawn.ts

The file (1,773 lines) contains zero imports of resolveCliRuntimeExecutionProvider. The child session entry is persisted with the raw split of the model ref:

// src/agents/subagent-spawn.ts:364-388 (persistInitialChildSessionRuntimeModel)
const { provider, model } = splitModelRef(params.resolvedModel);  // "anthropic"
...
store[target.canonicalKey] = mergeSessionEntry(store[target.canonicalKey], {
  model,
  ...(provider ? { modelProvider: provider } : {}),   // ← persists RAW "anthropic"
});

buildResolvedSubagentModelMetadata (line 288) reports the same raw value back to the caller — which is exactly the "resolvedProvider": "anthropic" we see in every spawn ack.

Call site 2 — src/gateway/server-methods/agent.ts

The handler (2,848 lines) also has zero resolver imports. Provider flows raw end-to-end:

// line 1120
const providerOverride = allowModelOverride ? request.provider : undefined;
// line 1422
const effectiveProvider = providerOverride || baseProvider;
// line 2350 — reads back the session entry that call site 1 persisted raw
const activeModelProvider =
  providerOverride ??
  resolveSessionModelRef(cfgForAgent ?? cfg, sessionEntry, activeSessionAgentId).provider;
// line ~2540 — dispatches with the raw provider
dispatchAgentRunFromGateway({ ingressOpts: { provider: providerOverride, ... } });

Why depth 2 is the victim

The run is registered (registerSubagentRun) before agent-runner.runtime's own resolver fires. Depth-1 spawns from a live main session go through reply-path admission that resolves the runtime independently. Depth-2 spawns (subagent → subagent) depend entirely on the session entry persisted by call site 1 — which holds modelProvider: "anthropic". With no cached CLI binding to fall back on, the launch path tries to honor a provider that is not an executable runtime, silently no-ops, and the sweeper declares "lost execution context" 30 minutes later. Empty transcript, zero tokens, parent pipeline stalls. Related symptom reports: #77720, #47975.

Proposed fix — same pattern as the merged #82857

src/auto-reply/reply/followup-runner.ts:808-823 is the maintainer-accepted template:

const cliExecutionProvider =
  (sessionRuntimeOverride && isCliProvider(sessionRuntimeOverride, runtimeConfig)
    ? sessionRuntimeOverride
    : undefined) ??
  resolveCliRuntimeExecutionProvider({
    provider, cfg: runtimeConfig, agentId: run.agentId,
    modelId: model, authProfileId: selectedAuthProfile.authProfileId,
  }) ??
  provider;

Apply it at both unpatched sites:

1. subagent-spawn.ts — resolve before persisting (in persistInitialChildSessionRuntimeModel, or at its call site where cfg and the target agent id are in scope):

import { resolveCliRuntimeExecutionProvider } from "./model-runtime-aliases.js";

const { provider: rawProvider, model } = splitModelRef(params.resolvedModel);
const provider = rawProvider
  ? (resolveCliRuntimeExecutionProvider({
      provider: rawProvider,
      cfg: params.cfg,
      agentId: params.agentId,   // needs the target agent id threaded in
      modelId: model,
    }) ?? rawProvider)
  : rawProvider;

This makes the child session entry carry the executable runtime (claude-cli) from the moment it exists — closing the registration→runner window for every depth, and fixing the misleading resolvedProvider in the spawn ack as a side effect.

2. server-methods/agent.ts — resolve activeModelProvider before run registration (line ~2350):

const rawActiveModelProvider =
  providerOverride ??
  resolveSessionModelRef(cfgForAgent ?? cfg, sessionEntry, activeSessionAgentId).provider;
const activeModelProvider =
  resolveCliRuntimeExecutionProvider({
    provider: rawActiveModelProvider,
    cfg: cfgForAgent ?? cfg,
    agentId: activeSessionAgentId,
  }) ?? rawActiveModelProvider;

resolveCliRuntimeExecutionProvider returns undefined for non-CLI providers (src/agents/model-runtime-aliases.ts:232), so both changes are no-ops for embedded/API-backed setups — only configs with a CLI runtime mapping are affected.

Acceptance criteria

  1. Depth-2 cold spawn (no prior CLI session binding for the target agent) with a canonical anthropic/... model on a claude-cli runtime: child launches a CLI process and produces a transcript.
  2. Child session entry persists modelProvider: "claude-cli" (or the configured CLI runtime) — never the raw canonical provider — when a CLI runtime is configured.
  3. Spawn ack resolvedProvider reflects the execution runtime.
  4. No behavior change when no CLI backend is configured (resolver returns undefined → falls back to raw provider).

Repro

  1. Configure a model with a CLI runtime (e.g. agents.defaults.models["anthropic/claude-sonnet-4-6"].agentRuntime.id = "claude-cli"), maxSpawnDepth >= 2.
  2. Spawn agent A from the main session; have A sessions_spawn agent B (mode: "run") — B must be an agent with no prior CLI session binding (fresh install or wipe cliSessionBindings).
  3. Observe: B registers, no CLI process starts, transcript stays empty, and after ~30 min the run fails with subagent run lost active execution context.
  4. Inspect B's session entry: modelProvider: "anthropic", no claudeCliSessionId, no cliSessionBindings — the signature.

Relationship to existing reports

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions