Skip to content

Fix sessions_spawn for subagent runtime with ACP-only fields#56342

Closed
shenkq97 wants to merge 1 commit into
openclaw:mainfrom
shenkq97:fix/subagent-ignore-acp-only-fields
Closed

Fix sessions_spawn for subagent runtime with ACP-only fields#56342
shenkq97 wants to merge 1 commit into
openclaw:mainfrom
shenkq97:fix/subagent-ignore-acp-only-fields

Conversation

@shenkq97

Copy link
Copy Markdown

Summary

  • Problem: sessions_spawn exposes ACP-only fields (streamTo, resumeSessionId) even when callers intend runtime="subagent".
  • Why it matters: schema-following models may emit those ACP-only fields, causing valid subagent spawns to fail before dispatch.
  • What changed: ignore ACP-only fields for non-ACP runtime instead of rejecting the request; update tests to lock in the forgiving subagent behavior.
  • What did NOT change (scope boundary): ACP runtime behavior, schema shape, or any other spawn semantics.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause / Regression History (if applicable)

  • Root cause: a mixed public sessions_spawn schema exposes ACP-only fields to tool-calling models, while execution rejects those same fields for runtime="subagent" before dispatch.
  • Missing detection / guardrail: no forgiving strip/ignore layer for ACP-only fields on the subagent path.
  • Prior context: community reports already describe the streamTo is only supported for runtime=acp failure on subagent spawns.
  • Why this regressed now: schema-following models are more likely to emit optional fields that appear in the public schema.
  • If unknown, what was ruled out: ruled out allowlist/config mistakes and subagent runtime implementation itself.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/tools/sessions-spawn-tool.test.ts
  • Scenario the test should lock in: runtime="subagent" should ignore streamTo / resumeSessionId and still call spawnSubagentDirect(...).
  • Why this is the smallest reliable guardrail: the failure happens in the tool dispatch guard before runtime execution, so a focused unit test covers the exact breakage point.
  • Existing test that already covers this (if any): existing sessions_spawn tool tests covered the prior reject behavior.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Subagent spawns become more robust when models include ACP-only fields that are irrelevant to runtime="subagent".

Diagram (if applicable)

Before:
LLM tool call -> sessions_spawn(runtime=subagent, streamTo=parent) -> guard rejects -> no spawn

After:
LLM tool call -> sessions_spawn(runtime=subagent, streamTo=parent) -> ACP-only fields ignored -> spawnSubagentDirect(...) -> success

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS (Apple Silicon)
  • Runtime/container: local host runtime
  • Model/provider: schema-following tool-call model behavior
  • Integration/channel (if any): N/A
  • Relevant config (redacted): sessions_spawn(runtime="subagent")

Steps

  1. Call sessions_spawn with runtime="subagent".
  2. Include streamTo: "parent" or resumeSessionId in the tool args.
  3. Observe pre-dispatch failure.

Expected

  • ACP-only fields are ignored for subagent runtime.
  • The request still reaches spawnSubagentDirect(...).

Actual

  • The request was rejected with streamTo is only supported for runtime=acp or resumeSessionId is only supported for runtime=acp.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: reproduced failing subagent spawn locally, applied equivalent workaround, confirmed subagent runs succeeded afterward; ran pnpm exec vitest run src/agents/tools/sessions-spawn-tool.test.ts and got 9/9 passing.
  • Edge cases checked: streamTo on subagent path; resumeSessionId on subagent path; ACP path left unchanged in code.
  • What you did not verify: full end-to-end hosted CI matrix.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

  • Risk: silently ignoring ACP-only fields could mask caller mistakes.
    • Mitigation: the ignored fields are already invalid for subagent runtime and currently hard-fail valid multi-agent use cases; ACP behavior remains unchanged.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 28, 2026
@greptile-apps

greptile-apps Bot commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR changes sessions_spawn to silently ignore the ACP-only fields streamTo and resumeSessionId when runtime="subagent" (or the default runtime), rather than returning a hard error. The motivation is that schema-following models may emit optional fields present in the shared schema even when they don't apply to the chosen runtime, causing valid subagent spawns to fail before dispatch.

  • Behavioral change is correctly scoped: the ACP path is unmodified; spawnSubagentDirect never received these fields before and still does not.
  • effectiveResumeSessionId / effectiveStreamTo variables are only consumed inside the if (runtime === \"acp\") block, where runtime === \"acp\" is already guaranteed, making them always equal to the originals at the use site. The stripping is implicit (the subagent path never reads those fields), so the new variables serve mainly as documentation of intent rather than actually changing runtime behavior.
  • Schema description mismatch: resumeSessionId's description still says Requires runtime=\"acp\", which implies a hard rejection. The actual behavior after this PR is "silently ignored," and the description should be updated to reflect that.
  • Tests correctly lock in the new forgiving behavior for both streamTo and resumeSessionId on the subagent path, and leave ACP-path tests unchanged.

Confidence Score: 5/5

Safe to merge — no P0/P1 issues; the fix is correctly scoped and well-tested.

All remaining findings are P2: a minor schema description mismatch and a slightly redundant variable pattern that doesn't affect correctness. The core behavioral change is sound, ACP behavior is untouched, and the new tests provide adequate coverage for the fixed subagent path.

No files require special attention beyond the minor schema description update in sessions-spawn-tool.ts.

Important Files Changed

Filename Overview
src/agents/tools/sessions-spawn-tool.ts Removes the hard-rejection guard for ACP-only fields (streamTo, resumeSessionId) on the subagent path and replaces it with a silent-strip via effectiveStreamTo / effectiveResumeSessionId. The actual stripping is implicit — neither field is forwarded to spawnSubagentDirect — so the effective* variables only matter inside the ACP branch where they always equal the originals. Minor: the resumeSessionId schema description still says 'Requires runtime=acp' rather than 'ignored for other runtimes'.
src/agents/tools/sessions-spawn-tool.test.ts Two previously rejection-asserting tests are updated to assert successful subagent spawns when ACP-only fields are present. The new assertions correctly verify that spawnSubagentDirectMock is called and spawnAcpDirectMock is not. ACP-path tests are untouched.

Comments Outside Diff (1)

  1. src/agents/tools/sessions-spawn-tool.ts, line 28-33 (link)

    P2 Schema description overstates the constraint after this change

    The resumeSessionId description still says Requires runtime="acp", which implies a hard rejection. After this PR the actual behavior is "silently ignored when runtime ≠ acp." A schema-following model that reads the description literally will still try to pair resumeSessionId with runtime="acp", which is the correct intent — but if it doesn't, the field is now quietly dropped rather than rejected. The description no longer accurately describes the enforcement semantics.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/tools/sessions-spawn-tool.ts
    Line: 28-33
    
    Comment:
    **Schema description overstates the constraint after this change**
    
    The `resumeSessionId` description still says `Requires runtime="acp"`, which implies a hard rejection. After this PR the actual behavior is "silently ignored when runtime ≠ acp." A schema-following model that reads the description literally will still try to pair `resumeSessionId` with `runtime="acp"`, which is the correct intent — but if it doesn't, the field is now quietly dropped rather than rejected. The description no longer accurately describes the enforcement semantics.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/tools/sessions-spawn-tool.ts
Line: 28-33

Comment:
**Schema description overstates the constraint after this change**

The `resumeSessionId` description still says `Requires runtime="acp"`, which implies a hard rejection. After this PR the actual behavior is "silently ignored when runtime ≠ acp." A schema-following model that reads the description literally will still try to pair `resumeSessionId` with `runtime="acp"`, which is the correct intent — but if it doesn't, the field is now quietly dropped rather than rejected. The description no longer accurately describes the enforcement semantics.

```suggestion
  resumeSessionId: Type.Optional(
    Type.String({
      description:
        'Resume an existing agent session by its ID (e.g. a Codex session UUID from ~/.codex/sessions/). Only meaningful with runtime="acp"; ignored for other runtimes. The agent replays conversation history via session/load instead of starting fresh.',
    }),
  ),
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/tools/sessions-spawn-tool.ts
Line: 133-134

Comment:
**`effective*` variables are always equal to the originals when consumed**

Both `effectiveResumeSessionId` and `effectiveStreamTo` are only ever read inside the `if (runtime === "acp")` block (lines 149 and 154). Because that guard guarantees `runtime === "acp"`, the ternary expression always resolves to the original value there — the `undefined` branch is dead code at the use site. The actual stripping happens implicitly because the subagent path never references those fields at all.

This isn't a bug, but it may cause future maintainers to believe the variables "do the stripping" when in fact they're unnecessary. A short inline comment or simply using the original variables directly inside the ACP block (relying on the existing guard) would be cleaner:

```ts
// ACP-only fields are not forwarded to the subagent path
// (spawnSubagentDirect never receives them).
```

or just continue using `resumeSessionId` / `streamTo` directly inside the `if (runtime === "acp")` block, which is already protected by the runtime guard.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Fix sessions_spawn for subagent runtime ..." | Re-trigger Greptile

Comment on lines +133 to +134
const effectiveResumeSessionId = runtime === "acp" ? resumeSessionId : undefined;
const effectiveStreamTo = runtime === "acp" ? streamTo : undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 effective* variables are always equal to the originals when consumed

Both effectiveResumeSessionId and effectiveStreamTo are only ever read inside the if (runtime === "acp") block (lines 149 and 154). Because that guard guarantees runtime === "acp", the ternary expression always resolves to the original value there — the undefined branch is dead code at the use site. The actual stripping happens implicitly because the subagent path never references those fields at all.

This isn't a bug, but it may cause future maintainers to believe the variables "do the stripping" when in fact they're unnecessary. A short inline comment or simply using the original variables directly inside the ACP block (relying on the existing guard) would be cleaner:

// ACP-only fields are not forwarded to the subagent path
// (spawnSubagentDirect never receives them).

or just continue using resumeSessionId / streamTo directly inside the if (runtime === "acp") block, which is already protected by the runtime guard.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/sessions-spawn-tool.ts
Line: 133-134

Comment:
**`effective*` variables are always equal to the originals when consumed**

Both `effectiveResumeSessionId` and `effectiveStreamTo` are only ever read inside the `if (runtime === "acp")` block (lines 149 and 154). Because that guard guarantees `runtime === "acp"`, the ternary expression always resolves to the original value there — the `undefined` branch is dead code at the use site. The actual stripping happens implicitly because the subagent path never references those fields at all.

This isn't a bug, but it may cause future maintainers to believe the variables "do the stripping" when in fact they're unnecessary. A short inline comment or simply using the original variables directly inside the ACP block (relying on the existing guard) would be cleaner:

```ts
// ACP-only fields are not forwarded to the subagent path
// (spawnSubagentDirect never receives them).
```

or just continue using `resumeSessionId` / `streamTo` directly inside the `if (runtime === "acp")` block, which is already protected by the runtime guard.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@martingarramon martingarramon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix — the forgiving-strip approach is the right call for schema-following models that emit all visible fields.

One thing I noticed: effectiveResumeSessionId and effectiveStreamTo are only read inside the if (runtime === "acp") branch, where they always equal the originals. So the variables serve as intent documentation rather than functional guards — you'd get the same behavior by just removing the two error-return blocks. Not a blocker, just an observation.

Could a debug-level log when stripping ACP-only fields be useful? Something like log.debug("Ignoring ACP-only field %s for runtime=%s", field, runtime) — would help operators debug cases where models emit unexpected parameters, without adding noise for users.

@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Closing this as duplicate or superseded after Codex automated review.

Close PR #56342 as superseded by open PR #68397. The bug is still present on current main, so this is not implemented-on-main: sessions_spawn still rejects streamTo and resumeSessionId for runtime="subagent", and tests still assert those rejection paths. #68397 is the newer active canonical PR for the same behavior, covers both ACP-only fields, updates the schema wording that Greptile flagged on #56342, and references the affected issue cluster.

Best possible solution:

Close #56342 as the older superseded PR, keep review and landing focused on #68397 or an equivalent maintainer patch that silently drops streamTo and resumeSessionId for non-ACP spawns, preserves ACP behavior, updates the schema wording, and replaces the rejection tests with regression coverage for successful subagent dispatch.

What I checked:

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Codex Review notes: model gpt-5.5, reasoning high; reviewed against 7e376e5aba32.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: sessions_spawn exposes ACP-only fields and breaks runtime=subagent with schema-following models

2 participants