Skip to content

Failed cron/autonomous turn shown as succeeded: cross-turn tool-error banner suppression (regression of #90122) #97849

Description

@yetval

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

  1. 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).
  2. Turn 1 ends in a terminally failed internal tool call and produces no assistant reply text (a genuinely failed run).
  3. Turn 2 (still agent-initiated, no user message in between) produces any assistant reply text.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExclude from stale automation

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions