Skip to content

interrupt queue mode replays previous assistant reply after abort #50145

@nano-jason

Description

@nano-jason

Bug Description

When messages.queue.mode is set to "interrupt", incoming messages that abort an active run cause the previous turn's assistant reply to be re-sent to the user, before the new (correct) reply is delivered.

Steps to Reproduce

  1. Set messages.queue.mode: "interrupt" in config
  2. Send message A to the bot
  3. While the bot is processing message A, quickly send message B
  4. Observe: the bot sends the previous conversation reply (from before message A), then sends the correct combined reply for A+B

Expected Behavior

When message B interrupts message A's run:

  • The aborted run should produce no output (silent exit)
  • Only message B's run should produce and deliver a reply

Actual Behavior

The aborted run delivers the last assistant message from session history as if it were the current run's output. This happens because:

  1. buildEmbeddedRunPayloads (src/agents/pi-embedded-runner/run/payloads.ts): When assistantTexts is empty (abort happened before model generated new text), it falls back to extractAssistantText(lastAssistant). But lastAssistant comes from messagesSnapshot (full session history), so it picks up the previous turn's reply.

  2. runReplyAgent (src/auto-reply/reply/agent-runner.ts): After an aborted run, the code still flushes blockReplyPipeline and processes payloadArray, potentially delivering stale content.

Proposed Fix

Fix 1: Guard lastAssistant fallback when aborted (run.ts)

In the call to buildEmbeddedRunPayloads, pass null for lastAssistant when the run was aborted and produced no new assistant text:

// Before:
lastAssistant: attempt.lastAssistant,

// After:
lastAssistant: (aborted && attempt.assistantTexts.length === 0) ? null : attempt.lastAssistant,

Fix 2: Early exit in runReplyAgent for aborted runs (agent-runner.ts)

Skip pipeline flush and payload delivery when the run was aborted:

const payloadArray = runResult.payloads ?? [];
const wasAborted = runResult.meta?.aborted === true;

if (blockReplyPipeline) {
  if (!wasAborted) await blockReplyPipeline.flush({ force: true });
  blockReplyPipeline.stop();
}

// ... existing code ...

if (wasAborted) {
  return finalizeWithFollowup(undefined, queueKey, runFollowupTurn);
}
if (payloadArray.length === 0) {
  return finalizeWithFollowup(undefined, queueKey, runFollowupTurn);
}

Environment

  • OpenClaw version: 2026.3.8+
  • Channels affected: All (Slack, WeChat, Telegram, etc.)
  • Queue mode: interrupt

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:not-repro-on-mainClawSweeper found high-confidence evidence that this issue no longer reproduces on main.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦪 silver shellfishThin issue quality; more reproduction proof or environment detail is needed.staleMarked as stale due to inactivity

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions