Skip to content

Fix TUI shells remaining open after exit - #9003

Closed
iarbpairs wants to merge 9 commits into
manaflow-ai:mainfrom
iarbpairs:fix/tui-shell-exit
Closed

iarbpairs wants to merge 9 commits into
manaflow-ai:mainfrom
iarbpairs:fix/tui-shell-exit

Conversation

@iarbpairs

@iarbpairs iarbpairs commented Jul 27, 2026 •

Copy link
Copy Markdown

Summary

  • include hosted child runtime in terminal-host Exit frames and surface-exited events, while accepting legacy empty payloads
  • match the Swift frontend's child-exit policy: retain startup failures at or below Ghostty's abnormal-command-exit-runtime, but explicitly close established shells
  • collapse the owning workspace when its last shell exits, so the last workspace exits the TUI
  • expose the optional runtime through remote sessions and the Rust binding

Problem

Typing exit in an established cmux-tui shell left a dead hosted terminal in durable topology. The frontend only removed that surface from its local tree; it never sent the explicit close/tombstone required by hosted terminals.

Verification

  • cargo +1.97.1 test -p cmux-tui-core — 404 passed
  • targeted cmux-tui exit, remote-event, Ghostty-config, and resize tests — 13 passed
  • cargo +1.97.1 check -p cmux-tui --all-targets
  • cargo +1.97.1 build --release -p cmux-tui
  • live PTY smoke: typed exit in the last shell; cmux-tui exited cleanly with status 0
  • combined local build with PR Fix TUI frame shrink race during drawing #8999: 140 rapid resize events including zero-width/zero-height frames, then exit; status 0 and no panic

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes TUI shells staying open after exit by auto-closing established hosted terminals and collapsing their workspace, so the TUI exits cleanly when the last shell finishes. Adds hosted child runtime_ms to exits to retain startup failures while closing established sessions.

  • Bug Fixes

    • Auto-close established hosted shells on child exit; remove dead surfaces; collapse the workspace when its last surface closes; quit when the final workspace closes.
    • Decide close-on-exit on the ordered worker after a fresh tree read to avoid races with concurrently added tabs; only close when the latest topology shows the last remaining surface.
    • Ignore stale lifecycle work: silently drop passive pointer motion/up events during layout changes, and treat late resize completions or local/remote “unknown surface N” errors as non-failures.
  • New Features

    • Include optional runtime_ms on surface-exited events and terminal-host Exit frames; accept legacy empty payloads.
    • Read Ghostty abnormal-command-exit-runtime (default 250ms) to retain short startup failures and close established exits.
    • Expose runtime_ms via remote sessions and all bindings (Go, Java, Python, TypeScript, Rust); update protocol docs.

Written for commit 83d9c10. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • surface-exited now optionally includes runtime_ms for hosted terminal children (exposed across supported bindings).
    • Added abnormal-command-exit-runtime_ms config support (default: 250 ms) to improve abnormal exit decisions.
  • Bug Fixes
    • Updated parsing/serialization, event forwarding/filtering, and surface shutdown trimming to correctly handle the new surface-exited payload shape when runtime_ms is missing/null.
  • Documentation
    • Refreshed the surface-exited event contract and compatibility notes for older servers.
  • Tests
    • Expanded protocol/runtime decoding, event matching, and surface/workspace closure coverage.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

SurfaceExited events now include optional hosted-process runtime metadata. Terminal hosts emit elapsed durations, protocol consumers preserve them, and the application uses a configurable abnormal-exit threshold to retain or close exited surfaces.

Changes

Surface exit runtime pipeline

Layer / File(s) Summary
Runtime payload contract and host emission
cmux-tui/crates/cmux-tui-core/src/{mux.rs,surface.rs,terminal_host_protocol.rs,terminal_host_runtime.rs}
Hosted terminal exits encode and decode optional millisecond runtimes, and SurfaceExited carries the resulting metadata.
Event transport and consumer contracts
cmux-tui/bindings/*, cmux-tui/crates/cmux-tui-core/{server.rs,event_bus.rs}, cmux-tui/crates/cmux-tui/src/session/remote.rs, cmux-tui/{docs,spec}/*
JSON, language bindings, remote sessions, mailbox handling, tests, and protocol documentation support the enriched event shape.
Exit threshold configuration and application handling
cmux-tui/crates/cmux-tui/src/{config.rs,app.rs,session/*}
Ghostty’s abnormal-exit runtime is resolved with a 250 ms fallback, and application handling retains or closes surfaces based on runtime and workspace state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TerminalHost
  participant SurfaceStager
  participant Mux
  participant App
  TerminalHost->>TerminalHost: Calculate elapsed runtime_ms
  TerminalHost->>SurfaceStager: Send Exit payload
  SurfaceStager->>Mux: Emit SurfaceExited { surface, runtime_ms }
  Mux->>App: Deliver surface-exited event
  App->>App: Compare runtime_ms with configured threshold
  App->>App: Retain or close exited surface
Loading

Possibly related PRs

  • manaflow-ai/cmux#8710: Updates related SurfaceExited handling in app.rs, including surface-only behavior.

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed No Swift files changed in this PR diff, so it cannot introduce or worsen Swift actor-isolation issues covered by the rule.
Cmux Swift Blocking Runtime ✅ Passed PASS: the PR diff contains no Swift files, so it cannot introduce or expand Swift blocking/timing synchronization.
Cmux Browser Automation Off-Main ✅ Passed Diff only changes Rust exit/workspace logic; no browser.* routing, WebKit/AppKit main-actor code, or policy tests were touched.
Cmux Expensive Synchronous Load ✅ Passed This PR only changes Rust app.rs; no Swift files or Swift main-actor/interactive loads are in scope, so the rule is not applicable.
Cmux Cache Substitution Correctness ✅ Passed The changed exit-close path uses session.refresh_tree() for the decision, not a cached snapshot, and the PR adds only runtime metadata handling.
Cmux No Hacky Sleeps ✅ Passed No production TS/JS/shell runtime adds sleeps or timers; the only wait-like usage is a test fixture, and the changed bindings are schema/parsing only.
Cmux Algorithmic Complexity ✅ Passed Added exit handling is linear tree lookup only; no new nested rescans or repeated hot-path sorting/filtering beyond existing behavior.
Cmux Swift Concurrency ✅ Passed No cmux-owned Swift files were changed; the PR diff only touches Rust, docs, and bindings, so the Swift-concurrency rule isn’t implicated.
Cmux Swift @Concurrent ✅ Passed No Swift files are changed in this PR, so the @concurrent review rule doesn't apply.
Cmux Swift Package Boundaries ✅ Passed No Swift files changed in the PR diff, so the Swift package-boundary rule is not implicated.
Cmux Swiftpm Lockfiles ✅ Passed The diff only changes cmux-tui/crates/cmux-tui/src/app.rs; no SwiftPM, Xcode, .gitignore, workflow, or Package.resolved files changed.
Cmux Swift Logging ✅ Passed No Swift files changed in the PR diff, so there are no production Swift logging changes to audit against the rule.
Cmux User-Facing Error Privacy ✅ Passed PASS: Changed user-visible text is generic; no added error/alert/output copy exposes vendor names, raw upstream messages, or secrets.
Cmux Full Internationalization ✅ Passed No Swift/UI or web locale assets changed; PR only updates Rust logic, bindings, and developer-facing protocol/spec docs.
Cmux Swiftui State Layout ✅ Passed The diff only touches Rust code; no Swift/SwiftUI files or state/layout patterns were introduced, so the SwiftUI rule is not triggered.
Cmux Architecture Rethink ✅ Passed No Swift files are changed in this PR, so the Swift architectural-rethink rule is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR changes only Rust/cmux-tui files; no Swift window/controller code or cmuxAuxiliaryWindowIdentifiers changes, so the rule doesn’t apply.
Cmux Source Artifacts ✅ Passed Only changed path is a Rust source file; the diff is handwritten code with no logs, caches, builds, or scratch artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The commit changes only cmux-tui/crates/cmux-tui/src/app.rs; no Swift Sources/**/*.swift files are in the diff, so the seam rule is not applicable.
Cmux No Ambient Global State ✅ Passed PR range contains no .swift files, so the no-ambient-global-state Swift rule is not implicated.
Title check ✅ Passed The title clearly matches the main change: fixing shells that stayed open after exit.
Description check ✅ Passed The description covers summary and verification well, but it omits the demo video, review trigger block, and checklist sections from the template.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR completes the hosted-shell exit cleanup flow.

  • Propagates optional child runtime metadata through terminal-host frames, mux events, remote sessions, protocol documentation, and all supported language bindings.
  • Retains short startup failures while explicitly closing established hosted shells.
  • Closes the owning workspace when its final shell exits, including surface-only attachments, allowing the final workspace to terminate the TUI.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported surface-only tombstone omission and cross-language binding inconsistency are addressed at the current head.

Important Files Changed

Filename Overview
cmux-tui/crates/cmux-tui/src/app.rs Implements runtime-aware exit handling, durable hosted-surface cleanup, and last-surface workspace collapse, including the surface-only attach path.
cmux-tui/crates/cmux-tui-core/src/mux.rs Extends surface-exit events with optional runtime metadata while preserving hosted terminal lifecycle semantics.
cmux-tui/crates/cmux-tui-core/src/terminal_host_protocol.rs Adds backward-compatible decoding and encoding of hosted child runtime in terminal-host exit frames.
cmux-tui/crates/cmux-tui/src/session/remote.rs Carries optional surface-exit runtime metadata through remote session event decoding.
cmux-tui/bindings/rust/src/lib.rs Exposes optional runtime metadata in the Rust binding without breaking legacy event decoding.
cmux-tui/bindings/go/types.go Adds nullable runtime metadata to Go surface events.
cmux-tui/bindings/python/cmux/client.py Adds optional runtime metadata to parsed Python events.
cmux-tui/bindings/typescript/src/protocol/events.ts Extends the typed surface-exited event with optional nullable runtime metadata.
cmux-tui/bindings/java/src/com/cmux/SurfaceEvent.java Exposes nullable runtime metadata through the Java surface-event record.

Sequence Diagram

sequenceDiagram
  participant Host as Terminal host
  participant Core as cmux-tui-core
  participant App as TUI frontend
  participant Tree as Durable topology
  Host-->>Core: Exit(runtime_ms)
  Core-->>App: SurfaceExited(surface, runtime_ms)
  alt Startup failure at or below threshold
    App->>App: Retain exited surface
  else Established shell
    alt Last surface in workspace
      App->>Tree: close-workspace
    else Workspace has other surfaces
      App->>Tree: close-surface
    end
    App->>App: Retire local surface state
  end
Loading

Reviews (2): Last reviewed commit: "fix(tui): complete exited surface cleanu..." | Re-trigger Greptile

Comment on lines +6357 to 6362
if self.surface_only == Some(surface) {
self.retire_surface_state(surface);
self.remove_surface_from_tree(surface);
self.quit = true;
return Ok(RenderAction::None);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Surface-only exit skips tombstone

When an established hosted PTY exits during cmux-tui attach --surface, this branch removes only the local state and quits before sending close-surface or close-workspace, causing the dead surface to remain in the server's durable workspace topology and reappear on a later attach.

Knowledge Base Used: cmux-tui

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 78361d5. Established surface-only exits now enqueue the same close mutation before quitting; the new regression verifies the workspace is removed.

Comment on lines +368 to +369
#[serde(default)]
pub runtime_ms: Option<u64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Binding event schemas remain inconsistent

The server and Rust binding add runtime_ms, but the Go, Python, TypeScript, and Java typed surface-exited models and conformance fixtures remain unchanged. Those consumers silently lose the metadata and cannot implement the documented established-shell versus startup-failure policy without bypassing their typed APIs.

Knowledge Base Used: cmux-tui

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 78361d5. runtime_ms now exists in the canonical spec and Rust, Go, Python, TypeScript, and Java event models, with parser/type coverage in every binding.

@iarbpairs

Copy link
Copy Markdown
Author

Addressed Greptile's findings in 78361d562b:

  • surface-only attaches now enqueue the explicit close/tombstone for established hosted shells before quitting; added a regression that verifies the owning workspace is removed
  • added runtime_ms to the canonical event spec and every typed binding (Rust, Go, Python, TypeScript, Java), with parser/type coverage in each binding

Reverified the TUI exit tests, all-target check, release build, and all five binding suites.

@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 `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 6363-6367: Update the runtime comparison in the abnormal
command-exit handling to use a strict “below threshold” check, so an exit
exactly at the configured threshold is treated as established/normal. Also
update startup_failure_exit_remains_visible_at_ghostty_threshold to use a
runtime value below the default threshold.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 602bfac3-f55a-4431-90c3-6250b046592d

📥 Commits

Reviewing files that changed from the base of the PR and between eedc4a6 and 9deb085.

📒 Files selected for processing (12)
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/crates/cmux-tui-core/src/event_bus.rs
  • cmux-tui/crates/cmux-tui-core/src/mux.rs
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui-core/src/terminal_host_protocol.rs
  • cmux-tui/crates/cmux-tui-core/src/terminal_host_runtime.rs
  • cmux-tui/crates/cmux-tui-core/tests/pty.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/session/remote.rs
  • cmux-tui/docs/protocol.md

Comment thread cmux-tui/crates/cmux-tui/src/app.rs

@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.

♻️ Duplicate comments (1)
cmux-tui/crates/cmux-tui/src/app.rs (1)

6356-6379: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Threshold comparison still treats an exit exactly at Ghostty's threshold as a startup failure.

This was already flagged in a prior review round: Ghostty's abnormal-command-exit-runtime is an exclusive threshold (exit strictly below the value is abnormal; exiting at exactly the threshold is normal/established). The current code still uses runtime_ms <= threshold to retain as a startup failure and runtime_ms > threshold to treat as established, which classifies an exit exactly at the configured threshold (e.g. the default 250ms) as abnormal instead of established. startup_failure_exit_remains_visible_at_ghostty_threshold (Line 16006) encodes this same off-by-one by asserting runtime_ms: Some(250) stays retained.

🐛 Suggested fix
-                    if runtime_ms.is_some_and(|runtime_ms| {
-                        runtime_ms > self.config.abnormal_command_exit_runtime_ms()
-                    }) {
+                    if runtime_ms.is_some_and(|runtime_ms| {
+                        runtime_ms >= self.config.abnormal_command_exit_runtime_ms()
+                    }) {
                         self.close_established_exited_surface(surface);
                     }
@@
-                if runtime_ms.is_some_and(|runtime_ms| {
-                    runtime_ms <= self.config.abnormal_command_exit_runtime_ms()
-                }) {
+                if runtime_ms.is_some_and(|runtime_ms| {
+                    runtime_ms < self.config.abnormal_command_exit_runtime_ms()
+                }) {
                     return Ok(RenderAction::Draw);
                 }

Then update startup_failure_exit_remains_visible_at_ghostty_threshold to use a runtime strictly below the default threshold (e.g. 249ms) so it still exercises the "retained" path.

Also applies to: 16006-16031

🤖 Prompt for 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.

In `@cmux-tui/crates/cmux-tui/src/app.rs` around lines 6356 - 6379, Correct the
threshold comparisons in the SurfaceExited handler so runtime_ms equal to
abnormal_command_exit_runtime_ms() is treated as established: use strictly below
for retaining startup failures and the complementary established condition for
closing them, including the surface_only path. Update
startup_failure_exit_remains_visible_at_ghostty_threshold to use a
below-threshold runtime such as 249ms.
🤖 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.

Duplicate comments:
In `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 6356-6379: Correct the threshold comparisons in the SurfaceExited
handler so runtime_ms equal to abnormal_command_exit_runtime_ms() is treated as
established: use strictly below for retaining startup failures and the
complementary established condition for closing them, including the surface_only
path. Update startup_failure_exit_remains_visible_at_ghostty_threshold to use a
below-threshold runtime such as 249ms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ca3f227c-b8dd-4b8c-9363-6da9e14fd9ce

📥 Commits

Reviewing files that changed from the base of the PR and between 9deb085 and 78361d5.

📒 Files selected for processing (12)
  • cmux-tui/bindings/go/events_test.go
  • cmux-tui/bindings/go/types.go
  • cmux-tui/bindings/java/src/com/cmux/CmuxEvent.java
  • cmux-tui/bindings/java/src/com/cmux/SurfaceEvent.java
  • cmux-tui/bindings/java/tests/com/cmux/JsonTest.java
  • cmux-tui/bindings/python/cmux/client.py
  • cmux-tui/bindings/python/tests/test_events.py
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/bindings/typescript/src/protocol/events.ts
  • cmux-tui/bindings/typescript/test/protocol-types.ts
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/spec/events.md

# Conflicts:
#	cmux-tui/crates/cmux-tui/src/app.rs

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmux-tui/bindings/go/types.go (1)

429-431: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer RuntimeMs over RuntimeMS for Go naming convention.

"MS" isn't a recognized Go initialism (unlike ID, URL, UUID), so RuntimeMS reads inconsistently with idiomatic Go and may trip staticcheck/revive naming checks. Since this is a brand-new exported field, renaming now is essentially free.

♻️ Proposed rename
-	RuntimeMS *uint64 `json:"runtime_ms"`
+	RuntimeMs *uint64 `json:"runtime_ms"`
🤖 Prompt for 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.

In `@cmux-tui/bindings/go/types.go` around lines 429 - 431, Rename the exported
field RuntimeMS to RuntimeMs in the relevant event type, preserving its *uint64
type and json:"runtime_ms" tag so the external JSON contract remains unchanged.
🤖 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 `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 5990-6008: Update close_established_exited_surface to derive the
last-tab decision from a freshly queried authoritative session tree, not the
render-cadence-bound self.tree/tab_locations snapshot. Preserve closing only the
exited surface unless the fresh mux state confirms it is the workspace’s sole
remaining tab; ensure the change cannot invoke close_workspace based on stale
cached state.
- Around line 17750-17838: Add tests covering both missing branches: verify an
established surface exit in a workspace with additional tabs closes only that
surface and preserves the workspace, and verify a quick/startup-failure exit in
surface_only mode quits without explicitly closing the surface. Exercise the
relevant MuxEvent::SurfaceExited handling and assert the resulting
workspace/surface state and app.quit behavior.

---

Outside diff comments:
In `@cmux-tui/bindings/go/types.go`:
- Around line 429-431: Rename the exported field RuntimeMS to RuntimeMs in the
relevant event type, preserving its *uint64 type and json:"runtime_ms" tag so
the external JSON contract remains unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 27d643f5-1c3a-4200-982e-a294eebe0e35

📥 Commits

Reviewing files that changed from the base of the PR and between 78361d5 and 513b0c1.

📒 Files selected for processing (12)
  • cmux-tui/bindings/go/types.go
  • cmux-tui/bindings/python/cmux/client.py
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/crates/cmux-tui-core/src/mux.rs
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui-core/src/terminal_host_protocol.rs
  • cmux-tui/crates/cmux-tui-core/src/terminal_host_runtime.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/session/remote.rs
  • cmux-tui/docs/protocol.md

Comment thread cmux-tui/crates/cmux-tui/src/app.rs Outdated
Comment thread cmux-tui/crates/cmux-tui/src/app.rs

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cmux-tui/bindings/python/cmux/client.py (3)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Resolve the new Ruff annotation warnings.

Ruff reports UP035 for typing.Iterator/Dict/List and UP007 for the new Union alias. If the declared Python target supports modern annotations, migrate consistently; otherwise confirm the Ruff target intentionally preserves compatibility.

Also applies to: 309-309

🤖 Prompt for 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.

In `@cmux-tui/bindings/python/cmux/client.py` at line 11, Update the type imports
and annotations in cmux’s Python client to resolve Ruff UP035 and UP007: replace
typing.Iterator, Dict, and List, and modernize Union annotations consistently if
the supported Python target allows it; otherwise configure or preserve the
intended compatibility target so Ruff does not flag these imports. Apply the
same change to the additional Union usage.

Source: Linters/SAST tools


1193-1201: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate newly typed protocol metadata at the JSON boundary.

float(...) accepts non-finite and out-of-range viewport widths, while runtime_ms accepts strings, floats, booleans, and negative values. These can violate the public dataclass contracts and break layout or exit-threshold consumers. Use strict parsing helpers and raise ProtocolError for malformed values.

Also applies to: 1269-1269

🤖 Prompt for 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.

In `@cmux-tui/bindings/python/cmux/client.py` around lines 1193 - 1201, Validate
protocol metadata at the JSON boundary before constructing the dataclass: update
parsing for viewport_base_width and ViewportSplit.width to use strict finite,
contract-range float helpers, and update runtime_ms parsing to accept only valid
non-negative integer values, rejecting strings, floats, booleans, and negatives.
Raise ProtocolError for every malformed or out-of-range value, while preserving
None and missing-field behavior where supported.

48-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject boolean viewport widths.

bool is an int subclass, so width=True passes both checks and sends JSON true instead of a numeric fraction. Reject booleans and non-numeric values before calling math.isfinite.

Proposed fix
 def _validate_viewport_pane_width(width: float) -> None:
-    if not math.isfinite(width) or not 0.1 <= width <= 1.0:
+    if (
+        isinstance(width, bool)
+        or not isinstance(width, (int, float))
+        or not math.isfinite(width)
+        or not 0.1 <= width <= 1.0
+    ):
         raise ValueError("viewport pane width must be between 0.1 and 1.0")
🤖 Prompt for 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.

In `@cmux-tui/bindings/python/cmux/client.py` around lines 48 - 50, Update
_validate_viewport_pane_width to reject boolean and non-numeric inputs before
calling math.isfinite, while preserving the existing finite 0.1–1.0 range
validation for numeric widths.
🤖 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 `@cmux-tui/crates/cmux-tui/src/app.rs`:
- Around line 6987-7013: Update close_established_exited_surface and both
SurfaceExited call paths so Remote sessions do not synchronously invoke
self.session.refresh_tree() on the event-loop thread. Move the
workspace-membership and last-tab decision to the background worker, then apply
close_surface or close_workspace after the refreshed tree is available, or
delegate that atomic decision to the mux while preserving the existing close
behavior.

---

Outside diff comments:
In `@cmux-tui/bindings/python/cmux/client.py`:
- Line 11: Update the type imports and annotations in cmux’s Python client to
resolve Ruff UP035 and UP007: replace typing.Iterator, Dict, and List, and
modernize Union annotations consistently if the supported Python target allows
it; otherwise configure or preserve the intended compatibility target so Ruff
does not flag these imports. Apply the same change to the additional Union
usage.
- Around line 1193-1201: Validate protocol metadata at the JSON boundary before
constructing the dataclass: update parsing for viewport_base_width and
ViewportSplit.width to use strict finite, contract-range float helpers, and
update runtime_ms parsing to accept only valid non-negative integer values,
rejecting strings, floats, booleans, and negatives. Raise ProtocolError for
every malformed or out-of-range value, while preserving None and missing-field
behavior where supported.
- Around line 48-50: Update _validate_viewport_pane_width to reject boolean and
non-numeric inputs before calling math.isfinite, while preserving the existing
finite 0.1–1.0 range validation for numeric widths.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9bf96e79-8cc9-44d0-a7bd-59ada7412ff7

📥 Commits

Reviewing files that changed from the base of the PR and between 7f37ecb and 45ed2c7.

📒 Files selected for processing (13)
  • cmux-tui/bindings/go/types.go
  • cmux-tui/bindings/java/tests/com/cmux/JsonTest.java
  • cmux-tui/bindings/python/cmux/client.py
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/bindings/typescript/src/protocol/events.ts
  • cmux-tui/bindings/typescript/test/protocol-types.ts
  • cmux-tui/crates/cmux-tui-core/src/mux.rs
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/session/remote.rs
  • cmux-tui/docs/protocol.md

Comment thread cmux-tui/crates/cmux-tui/src/app.rs Outdated
@iarbpairs

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 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.

@iarbpairs

Copy link
Copy Markdown
Author

@coderabbitai resume

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cmux-tui/bindings/rust/src/lib.rs (1)

1632-1645: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a legacy (no runtime_ms) decode test alongside this one.

This test only covers the field-present path. The sibling title_changed_decodes_authoritative_title test (right above, at line 1609) deliberately covers both the new-field and legacy-payload cases; this test should follow the same pattern given the PR explicitly targets accepting legacy empty surface-exited payloads.

✅ Suggested legacy-payload test
     #[test]
     fn surface_exited_decodes_hosted_runtime() {
         let event = parse_event(serde_json::json!({
             "event": "surface-exited",
             "surface": 7,
             "runtime_ms": 321,
         }));

         assert!(matches!(
             event,
             Event::SurfaceExited(SurfaceEvent { surface: 7, runtime_ms: Some(321) })
         ));
+
+        let legacy = parse_event(serde_json::json!({
+            "event": "surface-exited",
+            "surface": 7,
+        }));
+        assert!(matches!(
+            legacy,
+            Event::SurfaceExited(SurfaceEvent { surface: 7, runtime_ms: None })
+        ));
     }
🤖 Prompt for 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.

In `@cmux-tui/bindings/rust/src/lib.rs` around lines 1632 - 1645, Add a sibling
legacy-payload case to surface_exited_decodes_hosted_runtime that parses a
surface-exited event without runtime_ms and asserts Event::SurfaceExited with
the same surface and runtime_ms: None, matching the existing
title_changed_decodes_authoritative_title test pattern.
cmux-tui/crates/cmux-tui/src/session/mod.rs (1)

64-78: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use localized product text for stale layout errors.

rejection_message() is copied into LayoutUndoError::Stale, allowing raw remote/server text to reach the UI. Preserve the structured error code, but use the localized stale-layout catalog message instead.

As per coding guidelines, user-facing errors must not expose raw upstream messages or implementation details.

Suggested fix
-        Some(LayoutUndoError::STALE_CODE) => LayoutUndoError::Stale(
-            remote
-                .rejection_message()
-                .unwrap_or(crate::localization::catalog().layout.layout_changed_before_undo)
-                .to_string(),
-        )
-        .into(),
+        Some(LayoutUndoError::STALE_CODE) => LayoutUndoError::Stale(
+            crate::localization::catalog().layout.layout_changed_before_undo.to_string(),
+        )
+        .into(),
🤖 Prompt for 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.

In `@cmux-tui/crates/cmux-tui/src/session/mod.rs` around lines 64 - 78, Update
normalize_remote_layout_undo_error so the LayoutUndoError::Stale variant always
uses the localized catalog message layout_changed_before_undo, while preserving
the existing stale rejection code mapping and error behavior for other codes.

Source: Coding guidelines

🤖 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 `@cmux-tui/crates/cmux-tui-core/src/mux.rs`:
- Around line 6422-6431: Define a named constant for the unknown hosted runtime
sentinel and replace the bare u64::MAX in the SurfaceExited emission path with
that constant. Place the constant near the relevant mux runtime metadata
definitions so future comparisons can reuse the same symbol.

---

Outside diff comments:
In `@cmux-tui/bindings/rust/src/lib.rs`:
- Around line 1632-1645: Add a sibling legacy-payload case to
surface_exited_decodes_hosted_runtime that parses a surface-exited event without
runtime_ms and asserts Event::SurfaceExited with the same surface and
runtime_ms: None, matching the existing
title_changed_decodes_authoritative_title test pattern.

In `@cmux-tui/crates/cmux-tui/src/session/mod.rs`:
- Around line 64-78: Update normalize_remote_layout_undo_error so the
LayoutUndoError::Stale variant always uses the localized catalog message
layout_changed_before_undo, while preserving the existing stale rejection code
mapping and error behavior for other codes.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fabe5535-0fdc-4b12-a524-ccd95650880e

📥 Commits

Reviewing files that changed from the base of the PR and between 7f37ecb and 83d9c10.

📒 Files selected for processing (14)
  • cmux-tui/bindings/go/types.go
  • cmux-tui/bindings/java/tests/com/cmux/JsonTest.java
  • cmux-tui/bindings/python/cmux/client.py
  • cmux-tui/bindings/rust/src/lib.rs
  • cmux-tui/bindings/typescript/src/protocol/events.ts
  • cmux-tui/bindings/typescript/test/protocol-types.ts
  • cmux-tui/crates/cmux-tui-core/src/mux.rs
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/src/surface.rs
  • cmux-tui/crates/cmux-tui/src/app.rs
  • cmux-tui/crates/cmux-tui/src/config.rs
  • cmux-tui/crates/cmux-tui/src/session/mod.rs
  • cmux-tui/crates/cmux-tui/src/session/remote.rs
  • cmux-tui/docs/protocol.md

Comment on lines +6422 to +6431
self.emit(MuxEvent::SurfaceExited {
surface: id,
// Hosts predating runtime metadata still represent a process
// that reached the live stream, not a local/browser reap.
runtime_ms: Some(runtime_ms.unwrap_or(u64::MAX)),
});
return;
}
self.remove_surface_after_registry(id);
self.emit(MuxEvent::SurfaceExited(id));
self.emit(MuxEvent::SurfaceExited { surface: id, runtime_ms: None });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider naming the u64::MAX sentinel.

Hosts predating runtime metadata still represent a process that reached the live stream, not a local/browser reap. The logic is correct, but the bare u64::MAX magic value could be extracted into a named constant (e.g. UNKNOWN_HOSTED_RUNTIME_MS) to make the intent self-documenting at the call site and any future comparison sites.

🤖 Prompt for 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.

In `@cmux-tui/crates/cmux-tui-core/src/mux.rs` around lines 6422 - 6431, Define a
named constant for the unknown hosted runtime sentinel and replace the bare
u64::MAX in the SurfaceExited emission path with that constant. Place the
constant near the relevant mux runtime metadata definitions so future
comparisons can reuse the same symbol.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Closing this stale exit-lifecycle branch. Current main and merged #10839, #10855, and #11036 cover exit policy, output-after-exit, and receipt close. Re-cut only a proven hosted-child or last-shell behavior gap.

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