Summary
After PR #90122 (which closed #89683), a genuinely-failed agent-initiated turn (cron, scheduled, or autonomous continuation) is silently rendered as if it succeeded: its red "Tool error" banner is collapsed in the Control-UI and WebChat. The collapse leaks across the turn boundary because the success flag from a LATER turn's assistant reply is applied backward onto an EARLIER, genuinely-failed turn when no user message separates them.
Environment
Steps to reproduce
- Run a session where the agent takes two or more turns back-to-back with NO user message between them (cron job, scheduled task, or an autonomous continuation).
- Turn 1 ends in a terminally failed internal tool call and produces no assistant reply text (a genuinely failed run).
- Turn 2 (still agent-initiated, no user message in between) produces any assistant reply text.
- Open the session in the Control-UI / WebChat and look at turn 1's tool group.
Expected
Turn 1 failed and never produced an assistant reply, so its tool-error banner must stay visible (turnSucceeded must not be true). Only a turn that actually replied after its failed tool should be collapsed (the #90122 behavior).
Actual
Turn 1's failed tool group is stamped turnSucceeded = true, so its red "Tool error" banner is silently collapsed. The failed scheduled/autonomous run looks like it succeeded until the user manually expands the tool block. Turn 2's reply leaked backward across the turn boundary onto turn 1.
Root cause
annotateToolTurnOutcome in ui/src/ui/chat/build-chat-items.ts (function at line 353) walks the items backward and resets the sawAssistantReply flag ONLY when it crosses a role === "user" group:
function annotateToolTurnOutcome(
items: Array<ChatItem | MessageGroup>,
): Array<ChatItem | MessageGroup> {
let sawAssistantReply = false;
for (let i = items.length - 1; i >= 0; i -= 1) {
const item = items[i];
if (item.kind !== "group") {
continue;
}
const role = item.role.toLowerCase();
if (role === "user") {
sawAssistantReply = false;
} else if (role === "assistant") {
if (assistantGroupHasReplyText(item)) {
sawAssistantReply = true;
}
} else if (role === "tool") {
item.turnSucceeded = sawAssistantReply;
}
}
return items;
}
Agent-initiated turns (cron / scheduled / autonomous continuations) have no user group separating them, so the backward pass never resets sawAssistantReply between turns. A later turn's reply therefore carries backward and stamps an earlier, genuinely-failed tool turn as turnSucceeded = true. The user-only reset point was sufficient for the user-driven within-turn case in #90122 but not for agent-initiated multi-turn sequences.
Sibling surfaces
buildChatItems is the shared item builder, so every consumer that renders tool-turn outcome from it inherits the collapse: Control-UI and WebChat both reach it. The defect is in the shared annotation pass, not in a single channel renderer, so there is one root surface rather than per-channel siblings.
Real behavior proof
Behavior addressed: a genuinely-failed agent-initiated (cron / autonomous) turn is marked as succeeded, hiding its tool-error banner.
Real environment tested: drove the real exported buildChatItems from ui/src/ui/chat/build-chat-items.ts at commit be94853, with two back-to-back agent-initiated turns and no user message between them, then read back the turnSucceeded flag stamped on each tool group.
Exact steps or command run after this patch: built the two-turn message sequence (turn 1 = failed tool call, no reply; turn 2 = failed tool call + assistant reply text), passed it to buildChatItems, and wrote the resulting turnSucceeded of each tool group to /tmp/openclaw-bug-repros/audit-2026-06-29/observed.txt.
Evidence after fix:
# OBSERVED (buggy, origin/main be94853de0)
turn-1 (failed tool call-1, NO assistant reply): turnSucceeded=true
turn-2 (tool call-2 + assistant reply text): turnSucceeded=true
# EXPECTED (correct behavior on identical inputs)
turn-1 (failed tool call-1, NO assistant reply): turnSucceeded=false
turn-2 (tool call-2 + assistant reply text): turnSucceeded=true
Observed result after fix: turn 1, which failed and never replied, is stamped succeeded because turn 2's reply leaked backward across the unmarked agent-to-agent turn boundary; its error banner is wrongly collapsed.
What was not tested: a corrected run is not shown because the fix is non-trivial (a naive reset at every assistant boundary regresses the multi-tool single-turn collapse from #90122, so the boundary must be detected from turn metadata, not display structure); the EXPECTED values above are the required outcome on the identical inputs, not output from a patched build. Severity is display-correctness (silent and trust-breaking but recoverable by manually expanding the tool block), not data loss.
Reproduction artifact
The minimal repro that drives the real exported buildChatItems is kept at /tmp/openclaw-bug-repros/audit-2026-06-29/cross-turn-tool-error.test.ts. Running it against the tree at be94853 fails its turn-1 assertion ("turn-1 (failed, no reply) must NOT be marked succeeded: expected true not to be true") and writes the OBSERVED block above.
Summary
After PR #90122 (which closed #89683), a genuinely-failed agent-initiated turn (cron, scheduled, or autonomous continuation) is silently rendered as if it succeeded: its red "Tool error" banner is collapsed in the Control-UI and WebChat. The collapse leaks across the turn boundary because the success flag from a LATER turn's assistant reply is applied backward onto an EARLIER, genuinely-failed turn when no user message separates them.
Environment
ui/src/ui/chat/build-chat-items.ts), agent-initiated (cron / scheduled / autonomous) sessions with no user message between turns355c43fe0c), which fixed the within-turn case in [Bug]: Control UI chat surfaces failed internal tool calls as visible red error banners #89683Steps to reproduce
Expected
Turn 1 failed and never produced an assistant reply, so its tool-error banner must stay visible (
turnSucceededmust not betrue). Only a turn that actually replied after its failed tool should be collapsed (the #90122 behavior).Actual
Turn 1's failed tool group is stamped
turnSucceeded = true, so its red "Tool error" banner is silently collapsed. The failed scheduled/autonomous run looks like it succeeded until the user manually expands the tool block. Turn 2's reply leaked backward across the turn boundary onto turn 1.Root cause
annotateToolTurnOutcomeinui/src/ui/chat/build-chat-items.ts(function at line 353) walks the items backward and resets thesawAssistantReplyflag ONLY when it crosses arole === "user"group:Agent-initiated turns (cron / scheduled / autonomous continuations) have no
usergroup separating them, so the backward pass never resetssawAssistantReplybetween turns. A later turn's reply therefore carries backward and stamps an earlier, genuinely-failed tool turn asturnSucceeded = true. Theuser-only reset point was sufficient for the user-driven within-turn case in #90122 but not for agent-initiated multi-turn sequences.Sibling surfaces
buildChatItemsis the shared item builder, so every consumer that renders tool-turn outcome from it inherits the collapse: Control-UI and WebChat both reach it. The defect is in the shared annotation pass, not in a single channel renderer, so there is one root surface rather than per-channel siblings.Real behavior proof
Behavior addressed: a genuinely-failed agent-initiated (cron / autonomous) turn is marked as succeeded, hiding its tool-error banner.
Real environment tested: drove the real exported
buildChatItemsfromui/src/ui/chat/build-chat-items.tsat commit be94853, with two back-to-back agent-initiated turns and no user message between them, then read back theturnSucceededflag stamped on each tool group.Exact steps or command run after this patch: built the two-turn message sequence (turn 1 = failed tool call, no reply; turn 2 = failed tool call + assistant reply text), passed it to
buildChatItems, and wrote the resultingturnSucceededof each tool group to/tmp/openclaw-bug-repros/audit-2026-06-29/observed.txt.Evidence after fix:
Observed result after fix: turn 1, which failed and never replied, is stamped succeeded because turn 2's reply leaked backward across the unmarked agent-to-agent turn boundary; its error banner is wrongly collapsed.
What was not tested: a corrected run is not shown because the fix is non-trivial (a naive reset at every assistant boundary regresses the multi-tool single-turn collapse from #90122, so the boundary must be detected from turn metadata, not display structure); the EXPECTED values above are the required outcome on the identical inputs, not output from a patched build. Severity is display-correctness (silent and trust-breaking but recoverable by manually expanding the tool block), not data loss.
Reproduction artifact
The minimal repro that drives the real exported
buildChatItemsis kept at/tmp/openclaw-bug-repros/audit-2026-06-29/cross-turn-tool-error.test.ts. Running it against the tree at be94853 fails its turn-1 assertion ("turn-1 (failed, no reply) must NOT be marked succeeded: expected true not to be true") and writes the OBSERVED block above.