Skip to content

fix(agents): detect incomplete tool-use turns with pre-tool text (#76477)#76544

Merged
amknight merged 2 commits intomainfrom
ak/fix-76477-dropped-final-text
May 3, 2026
Merged

fix(agents): detect incomplete tool-use turns with pre-tool text (#76477)#76544
amknight merged 2 commits intomainfrom
ak/fix-76477-dropped-final-text

Conversation

@amknight
Copy link
Copy Markdown
Member

@amknight amknight commented May 3, 2026

Summary

Fixes #76477 — Agent tool-chain final text segments silently dropped.

When an agent turn contains tool calls (text → toolCall → toolResult → expected final text), the final text segment after the last tool result was silently dropped from both UI and next-turn context.

Root Cause

Two related bugs in src/agents/pi-embedded-runner/run/incomplete-turn.ts:

  1. resolveIncompleteTurnPayloadText immediately returned null ("turn is complete") when payloadCount !== 0. But pre-tool text (before the tool call) created payloads, so payloadCount > 0 suppressed the incomplete-turn check — even when the model expected to continue after tool results and the post-tool response was never produced.

  2. isIncompleteTerminalAssistantTurn only flagged incomplete turns when !hasAssistantVisibleText && stopReason === "toolUse". With pre-tool text existing, hasAssistantVisibleText was true, so even if reached, this check returned false.

The failing scenario

Assistant msg 1: text("initial analysis...") + toolCall("read")  →  stopReason: toolUse
Tool result arrives
Assistant msg 2: (never produced — WebSocket disconnect / session split)
→ lastAssistant still has stopReason: "toolUse"
→ assistantTexts: ["initial analysis..."], payloadCount: 1
→ resolveIncompleteTurnPayloadText returns null  →  turn "succeeds" with only pre-tool text
→ Post-tool final answer silently lost

Fix

isIncompleteTerminalAssistantTurn: Now returns true when stopReason === "toolUse" regardless of whether pre-tool text exists — a tool-use stop reason always means the model expected to continue after tool results.

resolveIncompleteTurnPayloadText: Added a toolUseTerminal guard so payloadCount !== 0 no longer suppresses the incomplete-turn check when the last assistant message ended with a tool call.

Tests

5 new tests added to run.incomplete-turn.test.ts:

  • isIncompleteTerminalAssistantTurn flags tool-use stop reason even with pre-tool text
  • resolveIncompleteTurnPayloadText detects tool-use terminal with pre-tool text as incomplete
  • Side-effect variant shows "verify before retrying" message
  • Completed turn with end_turn stopReason is correctly NOT flagged
  • Integration: runEmbeddedPiAgent surfaces error and logs warning for the scenario

All 90 tests in the file pass. All 4 related test files mentioned in the review pass.

)

When the last assistant message ended with stopReason=toolUse, pre-tool
text alone (payloadCount > 0) was suppressing the incomplete-turn guard.
The model expected to continue after tool results but the post-tool
response was never produced, silently dropping the final answer.

Fix isIncompleteTerminalAssistantTurn to always flag toolUse stop reason
as incomplete regardless of pre-tool text, and update the early-return
condition in resolveIncompleteTurnPayloadText to not skip the check when
the last assistant ended with a tool call.
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels May 3, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 3, 2026

Codex review: needs maintainer review before merge.

Summary
The PR updates incomplete-turn and lifecycle handling so terminal toolUse assistant turns with pre-tool text surface an error and abandoned state, with regression tests and a changelog entry for #76477.

Reproducibility: yes. Source inspection of current main gives a high-confidence reproduction path: an attempt with payloadCount: 1 and lastAssistant.stopReason: "toolUse" exits before incomplete-turn detection, and the lifecycle path remains working when visible pre-tool text exists.

Next step before merge
No automated repair is needed; the ready PR has no discrete review finding and should go through maintainer review, targeted tests, and normal landing gates.

Security
Cleared: Cleared: the diff is limited to agent runner/lifecycle logic, tests, and changelog text, with no dependency, workflow, secret, package, or external code-execution changes.

Review details

Best possible solution:

Land this focused runner/lifecycle fix with its changelog entry after maintainer review and targeted agent test validation.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection of current main gives a high-confidence reproduction path: an attempt with payloadCount: 1 and lastAssistant.stopReason: "toolUse" exits before incomplete-turn detection, and the lifecycle path remains working when visible pre-tool text exists.

Is this the best way to solve the issue?

Yes. The proposed fix is the narrow maintainable boundary: treat terminal toolUse as incomplete regardless of pre-tool text, preserve the existing payload guard for non-tool-use terminal turns, and align lifecycle state with regression coverage.

What I checked:

Likely related people:

  • vincentkoc: Current checkout blame attributes the incomplete-turn helper, terminal runner wiring, lifecycle derivation, and adjacent tests to commit c7bbb3f in the central affected files; local history is grafted, so deeper introduction history is less certain. (role: recent current-main maintainer; confidence: medium; commits: c7bbb3f9af36; files: src/agents/pi-embedded-runner/run/incomplete-turn.ts, src/agents/pi-embedded-runner/run.ts, src/agents/pi-embedded-subscribe.handlers.lifecycle.ts)

Remaining risk / open question:

  • This read-only review did not execute the PR's targeted Vitest files or verify remote CI status.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 9772ce6ce975.

…n lifecycle (#76477)

The lifecycle handler's derivedWorkingTerminalState was emitting
'working' for interrupted tool-use turns with pre-tool text because
it required !hasAssistantVisibleText for the 'abandoned' state.

Update the derivation to also mark as 'abandoned' when
incompleteTerminalAssistant is true, so lifecycle consumers see a
consistent state with the runner's terminal result.
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 3, 2026

🦞🦞
ClawSweeper is here and listening for maintainer commands.

Supported commands: /review, /clawsweeper status, /clawsweeper re-review, /clawsweeper implement, /clawsweeper build, /clawsweeper fix ci, /clawsweeper address review, /clawsweeper rebase, /clawsweeper autofix, /clawsweeper automerge, /clawsweeper approve, /autoclose <reason>, /clawsweeper explain, /clawsweeper stop.

I only act for maintainers, or for trusted ClawSweeper feedback on a ClawSweeper PR or PR opted into clawsweeper:autofix or clawsweeper:automerge.

@amknight amknight marked this pull request as ready for review May 3, 2026 10:43
@amknight
Copy link
Copy Markdown
Member Author

amknight commented May 3, 2026

/clawsweeper re-review

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented May 3, 2026

🦞🦞
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.

@amknight amknight merged commit a92e2b1 into main May 3, 2026
147 checks passed
@amknight amknight deleted the ak/fix-76477-dropped-final-text branch May 3, 2026 11:32
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…nclaw#76477) (openclaw#76544)

* fix(agents): detect incomplete tool-use turns with pre-tool text (openclaw#76477)

When the last assistant message ended with stopReason=toolUse, pre-tool
text alone (payloadCount > 0) was suppressing the incomplete-turn guard.
The model expected to continue after tool results but the post-tool
response was never produced, silently dropping the final answer.

Fix isIncompleteTerminalAssistantTurn to always flag toolUse stop reason
as incomplete regardless of pre-tool text, and update the early-return
condition in resolveIncompleteTurnPayloadText to not skip the check when
the last assistant ended with a tool call.

* fix(agents): mark tool-use terminal with pre-tool text as abandoned in lifecycle (openclaw#76477)

The lifecycle handler's derivedWorkingTerminalState was emitting
'working' for interrupted tool-use turns with pre-tool text because
it required !hasAssistantVisibleText for the 'abandoned' state.

Update the derivation to also mark as 'abandoned' when
incompleteTerminalAssistant is true, so lifecycle consumers see a
consistent state with the runner's terminal result.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…nclaw#76477) (openclaw#76544)

* fix(agents): detect incomplete tool-use turns with pre-tool text (openclaw#76477)

When the last assistant message ended with stopReason=toolUse, pre-tool
text alone (payloadCount > 0) was suppressing the incomplete-turn guard.
The model expected to continue after tool results but the post-tool
response was never produced, silently dropping the final answer.

Fix isIncompleteTerminalAssistantTurn to always flag toolUse stop reason
as incomplete regardless of pre-tool text, and update the early-return
condition in resolveIncompleteTurnPayloadText to not skip the check when
the last assistant ended with a tool call.

* fix(agents): mark tool-use terminal with pre-tool text as abandoned in lifecycle (openclaw#76477)

The lifecycle handler's derivedWorkingTerminalState was emitting
'working' for interrupted tool-use turns with pre-tool text because
it required !hasAssistantVisibleText for the 'abandoned' state.

Update the derivation to also mark as 'abandoned' when
incompleteTerminalAssistant is true, so lifecycle consumers see a
consistent state with the runner's terminal result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Agent tool-chain final text segments silently dropped — invisible to user and next-turn context

1 participant