Skip to content

assembly live: propagate a fatal cascade error's exit code from the TUI#255

Merged
alexkroman merged 3 commits into
mainfrom
claude/assembly-live-testing-vz59zi
Jun 19, 2026
Merged

assembly live: propagate a fatal cascade error's exit code from the TUI#255
alexkroman merged 3 commits into
mainfrom
claude/assembly-live-testing-vz59zi

Conversation

@alexkroman

Copy link
Copy Markdown
Collaborator

A fatal leg failure (STT/LLM/TTS) in the voice-only assembly live TUI was
caught on the worker thread (LiveAgentApp._run), shown as a brief inline
message, and then immediately followed by self.exit() — so the error flashed
away with the torn-down screen and, worse, the CLIError was swallowed: the
command returned normally and exited 0 even though the session had failed.

Record the error on the app and re-raise it from _run_live_tui once the app
has torn down, mirroring the engine's own cross-thread CascadeSession.error
record-then-re-raise. The command now exits with the error's code and renders
it to stderr (which survives the restored screen), matching the non-TUI path.

The non-TUI line renderer already propagated the error correctly; only the TUI
front-end broke the chain.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01PMa1QwLLA8atnrCrvtthTs

claude added 2 commits June 19, 2026 04:30
A fatal leg failure (STT/LLM/TTS) in the voice-only `assembly live` TUI was
caught on the worker thread (`LiveAgentApp._run`), shown as a brief inline
message, and then immediately followed by `self.exit()` — so the error flashed
away with the torn-down screen and, worse, the `CLIError` was swallowed: the
command returned normally and exited 0 even though the session had failed.

Record the error on the app and re-raise it from `_run_live_tui` once the app
has torn down, mirroring the engine's own cross-thread `CascadeSession.error`
record-then-re-raise. The command now exits with the error's code and renders
it to stderr (which survives the restored screen), matching the non-TUI path.

The non-TUI line renderer already propagated the error correctly; only the TUI
front-end broke the chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMa1QwLLA8atnrCrvtthTs
…uptible

The cascade's barge-in and the live TUI's Escape/Ctrl-C interrupt both keyed off
`self._reply.is_alive()` — a live reply worker. But the greeting is enqueued
directly in `greet()` with *no* worker, so during the spoken greeting Escape did
nothing and Ctrl-C fell through to quitting the whole session. The same gap left
the *tail* of a reply un-interruptible: once the worker finished enqueuing every
sentence and exited, its audio kept draining but `is_alive()` was already False.

Treat "the agent is audible" as worker-alive OR audio still queued
(`player.pending() > 0`), via a shared `_silence_if_speaking` helper used by both
`_barge_in` and `interrupt_reply`. Now a spoken turn or a keypress cuts the
greeting and a reply's trailing audio, not just a mid-flight worker. Setting the
stop flag with no worker running is harmless (the next `_start_reply` clears it).

Adds `pending()` to the `Player` protocol (the real `NullPlayer`/`_DuplexPlayer`
already implement it; `FakePlayer` gains it for the tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMa1QwLLA8atnrCrvtthTs
@alexkroman alexkroman enabled auto-merge June 19, 2026 05:44

def pending(self) -> int:
"""How many unplayed samples are still queued (>0 while audio is audibly playing)."""
...
The 0.3s voice-bar animation timer drives _render_voicebar, which can fire one
last tick during app teardown — after the DOM is dismantled but before the
interval is cancelled — so query_one("#voicebar") raised
`textual.css.query.NoMatches` and failed the py3.13 `lint + typecheck + tests`
cell (the code TUI's test_voice_on_mount). The #voicebar widget is composed
unconditionally, so a miss is definitionally a teardown artifact: guard the
query in _render_voicebar and no-op when the bar is gone. Both the code and live
TUIs share the pattern, so both are hardened (and pinned by a regression test
each that removes the bar then ticks).

Incidental tidy-ups made while keeping the files under the 500-line gate:
- code TUI: drop the one-line `_tick_voice` indirection and pass
  `_render_voicebar` straight to `set_interval`; hoist the 0.3s cadence to a
  `_TICK_SECONDS` class constant (mutation-exempt — a cosmetic value).
- merge the live TUI's two voice-bar tick tests into one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMa1QwLLA8atnrCrvtthTs
@alexkroman alexkroman added this pull request to the merge queue Jun 19, 2026
Merged via the queue into main with commit 78bead2 Jun 19, 2026
20 checks passed
@alexkroman alexkroman deleted the claude/assembly-live-testing-vz59zi branch June 19, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants