Skip to content

sessions_spawn rejects subagent runtime when streamTo is auto-filled by strict-mode providers #64714

Description

@kiraje

Summary

sessions_spawn with runtime="subagent" is effectively unusable from any LLM provider that auto-materializes optional schema fields (OpenAI strict-mode function calling, the GPT-5 family, OpenAI-compatible proxies that normalize tool schemas). Every call gets hard-rejected with:

streamTo is only supported for runtime=acp; got runtime=subagent

even when the agent explicitly does not want to set streamTo.

The issue is a structural interaction between the schema shape and a validator that treats "field present" as "user intent". It is not a user error and it cannot be worked around at the prompt layer.

Root cause

SessionsSpawnToolSchema.streamTo is defined as a single-value enum:

// dist/openclaw-tools-Co0S582U.js:7336
const SESSIONS_SPAWN_ACP_STREAM_TARGETS = ["parent"];
// dist/openclaw-tools-Co0S582U.js:7391
streamTo: optionalStringEnum(SESSIONS_SPAWN_ACP_STREAM_TARGETS),

Providers that strict-materialize optional fields (OpenAI strict mode, GPT-5.x, and OpenAI-compatible proxies) emit every optional field on every tool call. With a one-value enum, there is literally no value the model can pick that means "I don't want this field" — the only valid enum value is "parent", so streamTo always arrives as "parent".

The validator at dist/openclaw-tools-Co0S582U.js:7430-7433 then hard-rejects:

if (streamTo && runtime !== "acp") return jsonResult({
    status: "error",
    error: `streamTo is only supported for runtime=acp; got runtime=${runtime}`
});

Result: sessions_spawn(runtime="subagent") is impossible from these providers.

Reproduction

Using any OpenAI-compatible provider with auto-fill/strict function calling, have an agent call sessions_spawn with runtime="subagent". The agent will emit arguments like:

{
  "task": "...",
  "runtime": "subagent",
  "streamTo": "parent",
  "lightContext": true,
  "resumeSessionId": "",
  "attachAs": {"mountPath": ""}
}

Observe the {"status":"error","error":"streamTo is only supported for runtime=acp; got runtime=subagent"} response.

Evidence this is not model misbehavior

In a reproducing session on openclaw 2026.4.10, the agent's thinking content explicitly says "retry without streamTo", and its user-facing reply says "calling again without that field", yet the next tool-call arguments still contain streamTo: "parent". This rules out model intent as the cause — the field is being materialized by the provider's schema-filling layer downstream of the model's reasoning.

Cross-check with another tool in the same session: lcm_grep calls also ship since: "" and before: "" for date filters the agent never intended to set. Same auto-fill pattern, different tool. The agent literally cannot express absence of an optional field with this provider.

Over two sessions we measured: first session looped 33 times on the sessions_spawn error before giving up, a second session looped 6 more times after a /reset, a third session looped 4 more times with explicit "do not set streamTo" gotcha documentation loaded at bootstrap. 43 failed calls, zero successes.

Proposed fix

Accept streamTo silently when runtime !== "acp" instead of hard-rejecting. The intent of the guard is to prevent semantic misuse, but with a single-value enum that no caller can avoid materializing, the guard only prevents all use.

Minimal patch (shown against the dist file for clarity — the actual edit should be in src/agents/tools/sessions-spawn.ts):

- const streamTo = params.streamTo === "parent" ? "parent" : void 0;
+ const requestedStreamTo = params.streamTo === "parent" ? "parent" : void 0;
+ const streamTo = runtime === "acp" ? requestedStreamTo : void 0;
  const lightContext = params.lightContext === true;
  if (runtime === "acp" && lightContext) throw new Error("lightContext is only supported for runtime='subagent'.");
  // ... timeoutSecondsCandidate / runTimeoutSeconds / thread / attachments ...
- if (streamTo && runtime !== "acp") return jsonResult({
-     status: "error",
-     error: `streamTo is only supported for runtime=acp; got runtime=${runtime}`
- });

This keeps streamTo="parent" fully functional for runtime="acp" (the only runtime that actually supports it — the ACP branch at line 7438 is byte-identical after the patch) and silently drops it for runtime="subagent", which is the only way the field can ever arrive from auto-fill providers.

Note: resumeSessionId (line 7434-7437) has the same guard shape but does not have the same auto-fill problem — it's a free-form string and "" is the auto-filled default, which is falsy and does not trigger the guard. Leaving that validator alone is intentional.

Additional concerns

  1. Schema field has no description. Unlike lightContext ("Only applies to runtime='subagent'") and resumeSessionId ("Requires runtime='acp'"), streamTo ships without any description. Providers that do respect optionality still won't know the rule. Even after the validator is softened, adding a description would help those providers:

    streamTo: optionalStringEnum(SESSIONS_SPAWN_ACP_STREAM_TARGETS, {
      description: "Only applies to runtime='acp'. When 'parent', forwards child-run progress to the requester session as system events."
    }),
  2. Single-value enums in optional tool-schema fields are a general footgun for any tool consumed by strict-mode providers. Consider an internal lint/test that flags single-value enums in tool parameter schemas so this class of bug cannot recur silently.

Environment

  • openclaw 2026.4.10
  • macOS (Darwin 25.2.0)
  • Provider: OpenAI-compatible proxy → GPT-5 family model
  • Context: main agent session with runtime="subagent" spawn attempts

Workaround (local)

For anyone hitting this before a fix lands, a patch script for the minified dist file is included in the reproduction workspace. It needs root (/usr/local/lib/node_modules/...), restarts the gateway, and has to be re-applied on every npm i -g openclaw upgrade — it is a temporary unblock, not a fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions