Skip to content

[BUG] [v0.0.7] adapt_turn_aborted() maps TurnAbortReason::Replaced and ReviewEnded to StreamingError — normal turn replacement shown as error in UI (bridge/event_adapter/adapters.rs) #18540

Description

@echoforge2200

Summary

In src/cortex-tui/src/bridge/event_adapter/adapters.rs, adapt_turn_aborted() maps all three TurnAbortReason variants to AppEvent::StreamingError. Two of the three variants (Replaced and ReviewEnded) represent normal, expected conditions — not errors. This causes the TUI to display an error message to the user when no error occurred.

Root Cause

Lines 41-48:

pub fn adapt_turn_aborted(e: &TurnAbortedEvent) -> Option<AppEvent> {
    let reason = match e.reason {
        cortex_protocol::TurnAbortReason::Interrupted => "Turn interrupted by user",
        cortex_protocol::TurnAbortReason::Replaced => "Turn replaced by new request",
        cortex_protocol::TurnAbortReason::ReviewEnded => "Review mode ended",
    };
    Some(AppEvent::StreamingError(reason.to_string()))  // ← ALL variants → StreamingError
}

The three variants have different semantics:

  • Interrupted — User pressed Ctrl+C. This is intentional but could reasonably be shown as a warning/info.
  • Replaced — User sent a new message while the agent was still responding. The old turn is cancelled to process the new one. This is normal behavior, not an error.
  • ReviewEnded — Review mode ended normally. This is not an error.

Impact

When a user sends a new message while the agent is still responding (the most common case for Replaced), the TUI displays:

StreamingError: "Turn replaced by new request"

This is shown as an error in the UI (via AppEvent::StreamingError), alarming the user with a false error message for a completely normal interaction pattern. The same applies to ReviewEnded.

Reproduction

  1. Start a long agent response
  2. Send a new message before the response completes
  3. The UI displays "Turn replaced by new request" as a StreamingError — even though everything worked correctly

Fix

Map each variant to the appropriate event type:

pub fn adapt_turn_aborted(e: &TurnAbortedEvent) -> Option<AppEvent> {
    match e.reason {
        cortex_protocol::TurnAbortReason::Interrupted => {
            Some(AppEvent::StreamingError("Turn interrupted by user".to_string()))
        }
        cortex_protocol::TurnAbortReason::Replaced => {
            Some(AppEvent::StreamingCompleted)  // Normal — new message replaced old turn
        }
        cortex_protocol::TurnAbortReason::ReviewEnded => {
            Some(AppEvent::Info("Review mode ended".to_string()))  // Normal
        }
    }
}

File

src/cortex-tui/src/bridge/event_adapter/adapters.rs, adapt_turn_aborted function, lines 41-48

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions