Skip to content

fix: interrupting streaming response shows error message to user #447

Description

@avoidwork

Summary

When a user interrupts a streaming response by sending a new message while the agent is still streaming, an error message is displayed to the user instead of gracefully handling the interruption.

Environment

  • OS: Linux 7.0.2-7-pve
  • Node.js: v25.8.1
  • madz version: 1.18.2
  • LLM provider: Unknown — user to confirm

Reproduction

Steps to reproduce the behavior:

  1. Start a new session
  2. Send "hello"
  3. While the agent is streaming a response, send another "hello"
  4. Observe the error message displayed to the user

Expected Behavior

The streaming response should be gracefully cancelled and the new message processed without showing an error message to the user.

Actual Behavior

An error message is shown to the user (similar to the message in issue 444, but with a different root cause).

Additional Context

This is distinct from issue 444 — the symptom (error message to user) is similar, but the underlying cause is different. The issue occurs specifically when a new message is sent while a streaming response is in progress, causing an interruption that is not handled gracefully.

Audit Findings

  • File: src/agent/react.js:263-276 — When the abort signal is triggered, callReactAgentStreaming() checks signal.aborted inside the for await loop and returns { content: originalMessage }. However, if agent.streamEvents() throws an error before this check is reached (e.g., during stream initialization or between event iterations), the error propagates up to the caller.

  • File: src/tui/app.js:904-927handleChat()'s catch block checks err.name === "AbortError" to determine whether to show a clean "Interrupted." status or an error message. If the thrown error has a different name (e.g., DOMException with name "AbortError" in some environments, or a custom error from LangChain), the error message is displayed to the user.

  • File: src/tui/app.js:947-985handleInterrupt() is called when the user sends a new message while streaming. It aborts the controller and awaits the same dispatchPromise that handleChat() is awaiting. When the promise rejects, BOTH catch blocks are triggered (since they await the same promise). handleInterrupt() swallows the error silently, but handleChat()'s catch block still displays the error to the user.

  • Root cause hypothesis: The error thrown when the abort signal is triggered may not have name === "AbortError", causing handleChat()'s catch block to treat it as a real error rather than a graceful interruption. Additionally, the race condition between handleInterrupt() and handleChat() means the error is shown even though the interruption was intentional.

  • Suggested fix:

    1. Check for abort-related errors more broadly (e.g., check err.name for "AbortError", "Canceled", or check for abort-related properties)
    2. Or: Have handleInterrupt() signal to handleChat() that the interruption was intentional, so handleChat() doesn't show an error message
    3. Or: Use a shared state variable to track whether the abort was intentional (from handleInterrupt()) vs. accidental

Metadata

Metadata

Assignees

No one assigned

    Labels

    approvedAn identifier for Madz to take action.bugSomething isn't workingin progress

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions