Skip to content

fix(tui): guard raiser-side multiplexer calls in action handlers#148

Merged
pbean merged 2 commits into
mainfrom
fix/tui-guard-mux-raisers
Jul 15, 2026
Merged

fix(tui): guard raiser-side multiplexer calls in action handlers#148
pbean merged 2 commits into
mainfrom
fix/tui-guard-mux-raisers

Conversation

@pbean

@pbean pbean commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #143. Any raiser-side multiplexer seam call (one that raises MultiplexerError on a transport failure — has_session and friends) made from a Textual action handler with no try/except propagates out of the handler and crashes the whole TUI. Commit 8ed66e8 fixed one instance (attach_target_argv in _attach_to_target); this PR closes the rest of the class.

Auditing every handler, only calls routing to has_session were both unguarded and reachable. The other raiser-side methods are already guarded (attach_target_argv; new_*LaunchError), contract-sentinel-side (available, list_windows, current_pane_id, select_window, kill_window, target, …), or not reached from the TUI at all (send_text, list_window_ids, window_alive — engine/probe only). The dashboard poll's has_session was already guarded (tui/data.py).

Three fix sites, three contexts

Fix Site Change
A launch._ensure_ctl_session has_session sat outside the existing try, so its MultiplexerError escaped as-is and slipped past the launch/resume/resolve handlers' except launch.LaunchError. Moved inside the try → converts to LaunchError, hardening six handlers with zero app.py change.
B action_attach (the issue's named line ~356) The session_exists probe now routes through a new _mux_guarded helper that toasts the error and aborts the attach.
C _cleanup_sessions_worker prune_ctl_windows (probes has_session on the shared ctl session) is guarded on the worker thread with a call_from_thread toast + return.

_mux_guarded helper

A new BmadLoopApp._mux_guarded(probe) -> (ok, value) unifies the foreground convention: it backs both the new action_attach guard and the refactored _attach_to_target guard (behavior unchanged — the existing test_attach_multiplexer_error_notifies still passes). Fix A (LaunchError conversion, launch layer) and Fix C (worker call_from_thread) stay inline where the helper can't apply — Textual forbids call_from_thread from the main thread, so no single helper covers both.

Out of scope

The CLI cmd_attach path (cli.pylaunch.attach_plansession_exists) is also unguarded, but this issue is TUI-scoped ("crashes the whole TUI"); a one-shot CLI surfacing a traceback + nonzero exit is a lesser problem. A tidy follow-up could mirror how cmd_mux already handles MultiplexerError.

Tests

One behavioral test per fix, modeled on existing idioms:

  • Atest_ensure_ctl_session_probe_failure_raises_launch_error (mirrors test_new_window_failure_raises): a has-session transport failure converts to LaunchError.
  • Btest_attach_session_probe_error_notifies (mirrors test_attach_multiplexer_error_notifies): a raising session_exists yields a toast and a soft-failed attach, app stays on the dashboard.
  • Ctest_cleanup_sessions_mux_error_notifies: a raising prune_ctl_windows in the worker yields a toast, no crash.

Verification

  • Full suite: 2262 passed, 1 skipped.
  • ruff clean, trunk check clean.

Depends on #136/#137/#141/#142 (all merged).

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling when connecting to or managing terminal sessions.
    • The dashboard now displays helpful notifications instead of crashing when session operations fail.
    • Launch failures during terminal session setup are reported consistently.
    • Background session cleanup now fails gracefully while keeping the interface responsive.

Any raiser-side seam call (a method that raises MultiplexerError on a
transport failure — has_session and friends) made from a Textual action
handler with no try/except propagates out of the handler and crashes the
whole TUI. Commit 8ed66e8 fixed one instance (attach_target_argv); this
closes the rest of the class.

Auditing every handler, only calls routing to has_session were both
unguarded and reachable — the others are already guarded (attach_target_argv,
new_* via LaunchError), contract-sentinel-side (available, list_windows,
current_pane_id, select_window, kill_window, target, …), or not reached from
the TUI (send_text, list_window_ids, window_alive). Three sites, three
contexts:

- launch._ensure_ctl_session: has_session sat outside the existing try, so
  its MultiplexerError escaped as-is and slipped past the launch/resume/
  resolve handlers' `except LaunchError`. Moved inside → converts to
  LaunchError, hardening six handlers with no app.py change.
- action_attach: the session_exists probe (the issue's named crash) now
  routes through a new _mux_guarded helper that toasts and aborts.
- _cleanup_sessions_worker: prune_ctl_windows (probes has_session on the
  shared ctl session) guarded on the worker thread with a call_from_thread
  toast + return.

The _mux_guarded helper unifies the foreground convention: it backs both the
new action_attach guard and the refactored _attach_to_target guard. Fix A
(LaunchError) and Fix C (worker call_from_thread) stay inline where the
helper can't apply.

Tests: one behavioral test per fix, modeled on the existing idioms.

Closes #143
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pbean, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2ef47f0-d3cc-43cc-9dcb-26f03da64eac

📥 Commits

Reviewing files that changed from the base of the PR and between 0e68745 and 7b9f6fd.

📒 Files selected for processing (2)
  • src/bmad_loop/tui/app.py
  • tests/test_tui_app.py

Walkthrough

The TUI now centralizes foreground multiplexer error handling, reports cleanup failures safely from the worker thread, and converts control-session setup failures into LaunchError. New tests cover attach, cleanup, and session-probe failure paths.

Changes

TUI multiplexer error handling

Layer / File(s) Summary
Guard TUI multiplexer operations
src/bmad_loop/tui/app.py, tests/test_tui_app.py
A shared probe helper reports MultiplexerError through notifications; attach and cleanup flows abort or return safely, with smoke tests covering both paths.
Normalize control-session setup failures
src/bmad_loop/tui/launch.py, tests/test_tui_launch.py
Control-session probe and creation failures now raise LaunchError with the ctl-session setup failed message, covered by a transport-failure test.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

I’m a bunny with a guarded screen,
No tmux thump can spoil the scene.
Errors hop to toasts instead,
Cleanup treads where threads are led.
Softly fails the attach quest—
Dashboard stays, and all is best!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: guarding multiplexer calls in TUI action handlers.
Linked Issues check ✅ Passed The PR addresses the known crash path, adds a reusable guard, hardens related action/cleanup paths, and adds behavioral tests for the fixes.
Out of Scope Changes check ✅ Passed The changes stay focused on TUI multiplexer error handling and the related tests, with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-guard-mux-raisers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pbean

pbean commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/bmad_loop/tui/app.py`:
- Around line 929-936: Update the MultiplexerError handler in the worker cleanup
flow around launch.prune_ctl_windows so it sets windows to an empty list after
notifying the error instead of returning. Allow subsequent reporting logic to
continue and include sessions already processed by runs.prune_sessions alongside
the error toast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ecccc7e-fb79-4a65-9f0a-f1c409788ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 541a294 and 0e68745.

📒 Files selected for processing (4)
  • src/bmad_loop/tui/app.py
  • src/bmad_loop/tui/launch.py
  • tests/test_tui_app.py
  • tests/test_tui_launch.py

Comment thread src/bmad_loop/tui/app.py Outdated
Review follow-up (CodeRabbit on #148): the _cleanup_sessions_worker guard
returned early when prune_ctl_windows raised MultiplexerError, but
runs.prune_sessions had already killed the agent sessions by then. The early
return swallowed the "removed N session(s)" summary and, more importantly, the
"unverifiable engine pid (may still be live)" warning for pruned unknown-pid
sessions.

Set windows = [] instead of returning so the reporting tail still runs — the
ctl-window failure is surfaced as an error toast alongside the completed
session work. prune_ctl_windows raises in _ctl_window_candidates before any
kill_window, so zero windows are removed and [] is an honest count.

Strengthen test_cleanup_sessions_mux_error_notifies: it previously stubbed
prune_sessions to empties and only checked the error toast, so it passed with
or without the fix. It now returns a non-empty killed+unknown partition and
asserts the error toast, the unknown-pid warning, and the summary all land.
@pbean pbean merged commit 6f1abb4 into main Jul 15, 2026
9 checks passed
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.

TUI: unguarded raiser-side multiplexer calls in action handlers can crash the app

1 participant