fix(tui): prevent post-interrupt hang when ACP suppresses stale Completed#418
Merged
fix(tui): prevent post-interrupt hang when ACP suppresses stale Completed#418
Conversation
…nterrupt hang When a user interrupts (Escape) and then submits a new message, the TUI hangs forever with the spinner running indefinitely. The ACP backend suppresses stale Completed events via the turn_interrupted flag, but the TUI's on_interrupted_turn increments pending_stale_completes expecting to consume that event. Since the backend suppresses it, the counter is never drained, and the next real Completed is consumed as stale. Reset the counter to 0 in on_task_started so orphaned counters from backend-suppressed events don't block subsequent turns. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
theahura
added a commit
that referenced
this pull request
Apr 6, 2026
The previous approach used a shared AtomicBool flag to suppress stale TurnLifecycle::Completed events from cancelled tasks. This had an unavoidable TOCTOU race: when a user interrupted and quickly sent a new message, the flag was reset for the new turn before the old task could check it, allowing stale events to interfere with subsequent turns. Two prior attempts (PRs #416, #418) tried to fix this with variations of the same boolean+counter architecture. Each fix introduced a new bug: #416 caused hangs, #418 brought back hidden responses. This commit replaces the architecture entirely: - AtomicBool → AtomicU64 monotonic turn counter in ACP backend - Each spawned task captures its own turn ID at spawn time - ALL tail events (ErrorEvent + Completed + idle timer) are guarded by a single turn_id match check - TUI's pending_stale_completes counter is removed (no longer needed) The monotonic counter eliminates the race because it only increments— there is no "reset" that a stale task can observe. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
6 tasks
theahura
added a commit
that referenced
this pull request
Apr 6, 2026
The previous approach used a shared AtomicBool flag to suppress stale TurnLifecycle::Completed events from cancelled tasks. This had an unavoidable TOCTOU race: when a user interrupted and quickly sent a new message, the flag was reset for the new turn before the old task could check it, allowing stale events to interfere with subsequent turns. Two prior attempts (PRs #416, #418) tried to fix this with variations of the same boolean+counter architecture. Each fix introduced a new bug: #416 caused hangs, #418 brought back hidden responses. This commit replaces the architecture entirely: - AtomicBool → AtomicU64 monotonic turn counter in ACP backend - Each spawned task captures its own turn ID at spawn time - ALL tail events (ErrorEvent + Completed + idle timer) are guarded by a single turn_id match check - TUI's pending_stale_completes counter is removed (no longer needed) The monotonic counter eliminates the race because it only increments— there is no "reset" that a stale task can observe. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
🤖 Generated with Nori
turn_interruptedflag (suppresses staleCompleted) and TUI'spending_stale_completescounter (expects to consume it). The counter was never drained, so the next realCompletedwas consumed as stale.pending_stale_completes = 0inon_task_startedso orphaned counters from backend-suppressed events don't block subsequent turnsTest Plan
just fixandjust fmtcleanShare Nori with your team: https://www.npmjs.com/package/nori-skillsets