Skip to content

Fix helper read-screen terminal materialization - #6236

Closed
azooz2003-bit wants to merge 17 commits into
mainfrom
issue-helper-terminal-lifecycle
Closed

azooz2003-bit wants to merge 17 commits into
mainfrom
issue-helper-terminal-lifecycle

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1472

Summary:

  • surface.read_text stays non-materializing by default.
  • Explicit CLI read entrypoints pass start_if_needed: true and retry the transient terminal_not_ready error while cold helper terminals start.
  • Non-demand reads, including find-window --content and legacy tmux HUD metadata sniffing, pass start_if_needed: false so scans do not spawn hidden PTYs.
  • Terminal read readiness now lives at the terminal lifecycle boundary via ensureTerminalSurfaceReadyForRead.
  • Added V2 contract coverage for terminal_not_ready and start_if_needed, plus a live CLI regression for cold background helper panes.

Red/green:

  • Red test commit: 698a2fa test: cover helper read-screen materialization
  • Fix commit: 20e704c fix: materialize helper terminals for read-screen
  • Before fix: first explicit read-screen against a cold background helper could fail with internal_error: Failed to read terminal text; a later manual retry worked after the PTY appeared.
  • After fix: read-screen, capture-pane, and pipe-pane materialize separate cold helper surfaces, while tmux pane-command metadata probing remains non-materializing.

Checks run after rebasing onto current origin/main:

  • swift test --package-path Packages/CmuxControlSocket
  • swift test --package-path Packages/CmuxTerminal --filter TerminalSurfaceRestoreSpawnSchedulerTests
  • python3 -m py_compile tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • ./scripts/lint-ios-package-conventions.sh
  • ./tests/test_ci_pbxproj_test_wiring.sh
  • ./scripts/check-pbxproj.sh
  • git diff --check
  • ../../scripts/reload-cloud.sh --tag hread (no builder fleet configured; fell back to local tagged build)
  • CMUX_SOCKET_PATH=/tmp/cmux-debug-hread.sock CMUXTERM_CLI="$HOME/Library/Developer/Xcode/DerivedData/cmux-hread/Build/Products/Debug/cmux DEV hread.app/Contents/Resources/bin/cmux" python3 tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py
  • CMUX_SOCKET_PATH=/tmp/cmux-debug-hread.sock CMUXTERM_CLI="$HOME/Library/Developer/Xcode/DerivedData/cmux-hread/Build/Products/Debug/cmux DEV hread.app/Contents/Resources/bin/cmux" python3 tests_v2/test_cli_background_terminal_helpers_start_pty.py
  • /Users/abdulazizalbahar/Dev/Manaflow/cmuxterm-hq/skills/autoreview/scripts/autoreview --mode branch --base origin/main

Tagged proof:

  • Tag: hread
  • The rebased tagged app responded on /tmp/cmux-debug-hread.sock and was moved to LG HDR 4K.
  • Rebased cold-background proof: cmux-assets/issue-helper-terminal-lifecycle/rebased-hread-20260615-232646/transcript.txt shows read-screen materializing surface:18 while in_window=false, sending HREAD_REBASED_1781591206, reading the token output back, and top reporting zsh/gitstatus processes for the helper surface.
  • Visible web-helper proof: cmux-assets/issue-helper-terminal-lifecycle/rebased-hread-web-helper-20260615-233344/transcript.txt shows a focus-false helper pane (surface:19) in the live hread workspace accepting the same-worktree web dev-server command, starting local Postgres, applying migrations, reaching Next.js Ready, and then exiting visibly because local Stack secret keys are missing. Final surface-health keeps surface:19 in_window=true and top reports zsh/gitstatus processes for it.
  • Caller workspace proof previews: workspace:52, pane:105, surface:304 for the cold-background transcript and surface:305 for the visible web-helper transcript.
  • /cmux-assets was read-only on this machine, so durable artifacts use the worktree fallback under cmux-assets/issue-helper-terminal-lifecycle/.
  • Visual screenshot proof remains blocked locally: screencapture produced a black image and Computer Use could not attach to the tagged CG window. Socket/read-screen proof is complete.

No merge until explicit dogfood approval.

@vercel

vercel Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jun 16, 2026 7:07pm
cmux-staging Building Building Preview, Comment Jun 16, 2026 7:07pm

@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cold-terminal materialization support for surface.read_text: a new terminalNotReady(UUID) resolution case, an on-demand surface start API, preflight readiness gates in TerminalController, a retrying CLI helper (readScreenPayload), coordinator error mapping, unit tests, and an end-to-end Python regression test covering background workspace read-screen behavior.

Changes

Cold Surface Materialization and Retry

Layer / File(s) Summary
Resolution contract and surface start API
Packages/CmuxControlSocket/.../ControlSurfaceReadTextResolution.swift, Packages/CmuxTerminal/.../TerminalSurface+BackgroundStartLifecycle.swift
Adds terminalNotReady(UUID) enum case to ControlSurfaceReadTextResolution and requestReadDemandSurfaceStartIfNeeded() on TerminalSurface routing to requestSurfaceStartIfNeeded(source: .inputDemand, reason: "read-demand").
Protocol and test stub updates
Packages/CmuxControlSocket/.../ControlSurfaceContext.swift, Packages/CmuxControlSocket/Tests/.../ControlCommandContextTestStubs.swift, Packages/CmuxControlSocket/Tests/.../FakeSurfaceReadTextControlCommandContext.swift
Updates controlSurfaceReadText protocol signature to add startIfNeeded: Bool parameter; updates the existing test stub and adds the FakeSurfaceReadTextControlCommandContext test double capturing forwarded parameters.
Preflight readiness gates and terminal reader
Sources/TerminalController+TerminalReadDemand.swift, Sources/TerminalController+ControlSurfaceContext3.swift, Sources/TerminalController.swift
Adds ensureTerminalSurfaceReadyForRead and readTerminalTextBase64 to TerminalController; wires an early readiness guard into controlSurfaceReadText that returns .terminalNotReady(surfaceId) on failure; updates the Ghostty live surface path and CLI read path to pass startIfNeeded: true.
Coordinator parameter forwarding and error mapping
Packages/CmuxControlSocket/.../ControlCommandCoordinator+Surface2.swift
Parses and forwards start_if_needed to controlSurfaceReadText; maps .terminalNotReady resolution to a retryable err response with code terminal_not_ready, message "Terminal surface is starting", and surface_id in error data.
Unit tests
Packages/CmuxControlSocket/Tests/.../ControlCommandCoordinatorSurfaceReadTextTests.swift
Adds three tests verifying terminalNotReady error mapping and start_if_needed true/false propagation through the coordinator.
CLI helpers for screen reading with retry
CLI/CMUXCLI+ReadScreen.swift, CLI/CMUXCLI+TmuxCompatSupport.swift
Adds readScreenPayload with up-to-60-retry loop on terminal_not_ready errors, contentSearchTerminalText for single best-effort reads, and extends legacy HUD detection with start_if_needed: false.
CLI call site updates
CLI/cmux.swift
Replaces five inline client.sendV2(surface.read_text, ...) calls with readScreenPayload(client:params:) or contentSearchTerminalText(workspaceId:client:).
End-to-end regression test
tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py
Python regression test that finds an unhosted background workspace, creates terminal surfaces and panes, verifies read-screen materializes cold surfaces, asserts tmux metadata probes do not materialize surfaces, and validates workspace focus is unchanged throughout.
Xcode project registration
cmux.xcodeproj/project.pbxproj
Registers CMUXCLI+ReadScreen.swift and TerminalController+TerminalReadDemand.swift in their respective build targets.

Sequence Diagram

sequenceDiagram
  participant CLI as cmux CLI
  participant Socket as SocketClient
  participant Coord as ControlCommandCoordinator
  participant TC as TerminalController
  participant Surface as TerminalSurface

  CLI->>CLI: readScreenPayload(params, start_if_needed=true)
  loop up to 60 retries
    CLI->>Socket: surface.read_text(start_if_needed=true)
    Socket->>Coord: surfaceReadText(start_if_needed=true)
    Coord->>TC: controlSurfaceReadText(startIfNeeded=true)
    TC->>TC: ensureTerminalSurfaceReadyForRead preflight
    alt surface cold
      TC->>Surface: requestReadDemandSurfaceStartIfNeeded()
      TC->>TC: re-check readDemand live surface
      TC-->>Coord: .terminalNotReady(surfaceId)
      Coord-->>Socket: err(terminal_not_ready, retryable=true)
      Socket-->>CLI: CLIError "terminal_not_ready: ..."
      CLI->>CLI: sleep 100ms, retry
    else surface ready
      TC-->>Coord: .success(snapshot)
      Coord-->>Socket: ok(payload)
      Socket-->>CLI: payload
      CLI-->>CLI: return payload
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5243: Modifies the same readTerminalTextBase64 and terminal text snapshot/payload generation logic in Sources/TerminalController.swift that this PR rewires for demand-start behavior.

Suggested reviewers

  • lawrencecchen

🐇 A cold terminal sits still in the night,
No PTY burning, no shell in sight.
"read-screen!" cries the agent — the rabbit retries,
Sixty short sleeps 'til the surface replies.
The workspace awakens, the text scrolls through,
Background or not, every screen comes to view! 🌱


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error CLI/CMUXCLI+ReadScreen.swift introduces usleep(100_000) in production code within a polling retry loop, violating swift-blocking-runtime rules that flag sleep-based timing. Replace sleep-based polling with real signals: use a notification, state callback, or async/await mechanism instead of the while-true loop with usleep retry.
Cmux Full Internationalization ❌ Error PR adds unlocalized user-facing V2 API error message "Terminal surface is starting" without following the required pattern of ControlSurfaceInputStrings localization used for similar error messages. Create ControlSurfaceReadTextStrings struct following the ControlSurfaceInputStrings pattern; localize the message via String(localized:key, defaultValue:) and add entries to Resources/Localizable.xcstrings for all supported locales.
Cmux Architecture Rethink ❌ Error The PR introduces a timing repair path using usleep(100_000) polling in CMUXCLI+ReadScreen.swift:20 to paper over a MainActor lifecycle race. ensureTerminalSurfaceReadyForRead schedules an unawaite... Make ensureTerminalSurfaceReadyForRead async and await the surface start Task, or restructure to ensure Task completes before readiness is checked. Document the state transition owner and invariant preventing synchronous readiness checks.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix helper read-screen terminal materialization' accurately summarizes the main change: enabling on-demand PTY materialization for background helper terminals when read-screen is executed.
Linked Issues check ✅ Passed The PR fully addresses issue #1472 by implementing on-demand PTY materialization for background workspaces via the start_if_needed parameter and transient terminal_not_ready error handling.
Out of Scope Changes check ✅ Passed All code changes are directly related to the stated objectives: adding start_if_needed parameter support, implementing retry logic for transient errors, and creating readiness validation helpers.
Cmux Swift Actor Isolation ✅ Passed All production code properly respects Swift 6 actor isolation: CLI/CMUXCLI+ReadScreen.swift is a struct extension with no shared mutable state; TerminalController+TerminalReadDemand.swift methods i...
Cmux Expensive Synchronous Load ✅ Passed The PR does not add expensive synchronous loaders (disk I/O, per-record syscalls, or directory scans) onto main-actor or interactive paths. New functions perform only network calls, property access...
Cmux Cache Substitution Correctness ✅ Passed PR adds fresh-read helpers for transient CLI operations (read-screen, find-window, capture-pane, pipe-pane) and control surface API, not persistence paths. Session scrollback persistence via readTe...
Cmux No Hacky Sleeps ✅ Passed No violations of runtime-no-hacky-sleeps.md found. Python test file contains no production sleeps; Swift usleep(100_000) is explicitly excluded from this rule per swift-blocking-runtime.md.
Cmux Algorithmic Complexity ✅ Passed PR introduces bounded retry logic (60 attempts max) with fixed 0.1s sleep, fixed-size test loops (max 16 iterations), and no nested collection scans or per-item rescans violating algorithmic comple...
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. New code is either pure synchronous CLI/business logic or necessary AppKit MainActor integration, complying with modernization guidelines.
Cmux Swift @Concurrent ✅ Passed All new and modified Swift functions are synchronous with no async/await or @concurrent annotations. All are called from synchronous contexts and comply with `.github/review-bot-rules/swift-concurr...
Cmux Swift File And Package Boundaries ✅ Passed PR adds two small focused Swift extensions (37 and 48 lines) with clear single responsibilities. Modifications to oversized files are incidental and net-negative for TerminalController.swift. All c...
Cmux Swift Logging ✅ Passed No Swift logging violations found. New production code contains no problematic logging; DEBUG-guarded logs are compliant per rules; CLI output appropriately uses print().
Cmux User-Facing Error Privacy ✅ Passed All user-facing error messages in the PR ("Terminal surface is starting", "Terminal surface not found", "Failed to read terminal text") use generic product-level terminology with no vendor/internal...
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI state additions: no @Published/@StateObject/@Observable/@EnvironmentObject patterns, no problematic GeometryReader, lazy lists, or render-time mutations detected across all c...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds no NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup code that would require window identifier registration or close-shortcut ownership compliance.
Cmux Source Artifacts ✅ Passed All 15 changed files are intentional source code, tests, and build config; no prohibited artifacts (logs, screenshots, DerivedData, caches, etc.) are present in the diff.
Description check ✅ Passed The pull request description comprehensively addresses the objectives, includes testing details, red-green validation, and specific validation proof across multiple scenarios.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-helper-terminal-lifecycle

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes cold-start materialization for background helper terminals: surface.read_text now accepts a start_if_needed flag that triggers synchronous surface startup and, if the surface isn't ready yet, returns a retryable terminal_not_ready error that the socket worker waits out using a new SurfaceReadTextReadinessWaiter actor backed by withCheckedContinuation and the terminalSurfaceDidBecomeReady notification.

  • Explicit CLI read entrypoints (read-screen, capture-pane, pipe-pane) now pass start_if_needed: true and transparently materialize cold helper surfaces, while non-demand paths like find-window --content and tmux HUD metadata probes pass start_if_needed: false to avoid spawning hidden PTYs.
  • ControlCommandExecutionPolicy gains parameter-sensitive routing so demand reads are dispatched to the socket worker rather than the main actor, and TerminalSurface+BackgroundStartLifecycle gains a synchronous requestReadDemandSurfaceStartIfNeeded() that bypasses the background scheduler for immediate headless startup.
  • A new V2 live regression test covers all four CLI entry points against a cold background helper and explicitly verifies that tmux pane-command metadata probing does not materialize surfaces as a side effect.

Confidence Score: 4/5

Safe to merge after adding the missing locale translations for socket.terminal.surfaceStarting; all other changes are sound.

The new socket.terminal.surfaceStarting error string is user-facing and the string catalog ships 21 locales, but the new key only has en and ja entries — the remaining 19 locales fall back to English. The rest of the fix is well-constructed: the actor-based readiness waiter correctly handles the pending→ready race, the startup guard (surface == nil) makes repeated probes idempotent, and the parameter-sensitive execution-policy routing is covered by tests.

Resources/Localizable.xcstrings — missing locale translations for the new socket.terminal.surfaceStarting key.

Important Files Changed

Filename Overview
Sources/SurfaceReadTextReadinessWaiter.swift New actor implementing withCheckedContinuation-based readiness signaling for cold terminal starts; handles pending→ready race correctly and cleans up observers properly.
Sources/TerminalController+SurfaceReadTextSocketWorker.swift New socket-worker extension implementing the three-probe retry sequence (initial call → register waiter → second call → wait for ready notification → final read); uses pre-existing v2MainSync (DispatchQueue.main.sync) for each coordinator hop, which is consistent with the codebase pattern.
Sources/TerminalController+TerminalReadDemand.swift New file consolidating ensureTerminalSurfaceReadyForRead and readTerminalTextBase64 helpers; startIfNeeded flag correctly gates cold startup and the logic is straightforward.
Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift Adds requestReadDemandSurfaceStartIfNeeded() by extracting startSurfaceImmediatelyIfNeeded from the async closure into a synchronous @mainactor helper; startup guard (surface == nil) makes repeated calls idempotent.
Packages/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift Adds parameter-sensitive routing for surface.read_text (demand reads with start_if_needed:true go to socket worker); controlCommandExecutionPolicyBoolValue duplicates v2BoolValue from the app target.
Resources/Localizable.xcstrings New socket.terminal.surfaceStarting key added with only en and ja translations; the catalog supports 21 locales — 19 are missing entries for this key.
Sources/TerminalController+ControlSurfaceContext3.swift Wires startIfNeeded through controlSurfaceReadText and adds controlSurfaceReadTextStrings() resolver; guard structure for the not-ready vs. legacy error paths is correct.
CLI/CMUXCLI+ReadScreen.swift New file cleanly separates demand read (start_if_needed:true) from content-search read (start_if_needed:false); entrypoints in cmux.swift are updated correctly.
tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py Comprehensive live regression test covering read-screen, capture-pane, pipe-pane materialization for cold background helpers and verifying tmux pane-command metadata probe does not materialize surfaces.
.github/workflows/ci.yml SPM cache keys updated to v1 versioning and narrowed to checkouts/repositories subdirs; artifact-failure recovery now removes the whole source-packages dir for a clean retry.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CLI as CLI (read-screen)
    participant SW as Socket Worker
    participant RW as SurfaceReadTextReadinessWaiter
    participant MA as MainActor / Coordinator
    participant TS as TerminalSurface

    CLI->>SW: "surface.read_text {start_if_needed: true}"
    SW->>MA: v2MainSync → controlSurfaceReadText(startIfNeeded: true)
    MA->>TS: ensureTerminalSurfaceReadyForRead → requestReadDemandSurfaceStartIfNeeded()
    TS-->>MA: "surface == nil → scheduleHeadlessStart (async)"
    MA-->>SW: .terminalNotReady(surfaceID)
    SW->>RW: prepareWait(for: surfaceID) → waiter(.pending)
    SW->>MA: v2MainSync → controlSurfaceReadText (retry, still cold)
    MA-->>SW: .terminalNotReady(surfaceID) again
    SW->>RW: wait(readinessWait) — awaits continuation
    TS-->>MA: surface becomes ready
    MA->>SW: NotificationCenter posts terminalSurfaceDidBecomeReady
    SW->>RW: signalReady → resume continuation(true)
    RW-->>SW: wait() → true
    SW->>MA: v2MainSync → controlSurfaceReadText (final read)
    MA-->>SW: .read(text, base64, …)
    SW-->>CLI: "OK {text: …}"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CLI as CLI (read-screen)
    participant SW as Socket Worker
    participant RW as SurfaceReadTextReadinessWaiter
    participant MA as MainActor / Coordinator
    participant TS as TerminalSurface

    CLI->>SW: "surface.read_text {start_if_needed: true}"
    SW->>MA: v2MainSync → controlSurfaceReadText(startIfNeeded: true)
    MA->>TS: ensureTerminalSurfaceReadyForRead → requestReadDemandSurfaceStartIfNeeded()
    TS-->>MA: "surface == nil → scheduleHeadlessStart (async)"
    MA-->>SW: .terminalNotReady(surfaceID)
    SW->>RW: prepareWait(for: surfaceID) → waiter(.pending)
    SW->>MA: v2MainSync → controlSurfaceReadText (retry, still cold)
    MA-->>SW: .terminalNotReady(surfaceID) again
    SW->>RW: wait(readinessWait) — awaits continuation
    TS-->>MA: surface becomes ready
    MA->>SW: NotificationCenter posts terminalSurfaceDidBecomeReady
    SW->>RW: signalReady → resume continuation(true)
    RW-->>SW: wait() → true
    SW->>MA: v2MainSync → controlSurfaceReadText (final read)
    MA-->>SW: .read(text, base64, …)
    SW-->>CLI: "OK {text: …}"
Loading

Reviews (12): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread CLI/cmux.swift Outdated
Comment on lines +4780 to +4790
while true {
do {
payload = try client.sendV2(method: "surface.read_text", params: params)
break
} catch let error as CLIError
where error.message.hasPrefix("terminal_not_ready:") && readAttempt < 60 {
readAttempt += 1
usleep(100_000)
continue
}
}

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 usleep polling loop in production Swift — The retry loop uses usleep(100_000) up to 60 times (≤ 6 s of wall-clock blocking) to synchronize on terminal startup. This is a fixed sleep used as synchronization, which the cmux-swift-blocking-runtime rule prohibits in shipped Swift code regardless of duration. The real readiness event here is TerminalSurface.surface transitioning from nil to non-nil after the queued Task executes; the CLI is polling across a socket boundary instead of waiting on that signal. A conforming approach would have the server block internally until the surface is ready (or a caller-supplied deadline expires) before responding, removing the need for any timed retry on the client side.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

Comment thread Sources/TerminalController.swift Outdated
Comment on lines 5008 to 5017
private func readTerminalTextBase64(terminalPanel: TerminalPanel, includeScrollback: Bool = false, lineLimit: Int? = nil) -> String {
if terminalPanel.surface.liveSurfaceForGhosttyAccess(reason: "readTerminalTextBase64.preflight") == nil {
terminalPanel.surface.requestReadDemandSurfaceStartIfNeeded()
guard terminalPanel.surface.liveSurfaceForGhosttyAccess(reason: "readTerminalTextBase64.readDemand") != nil else {
return "ERROR: Terminal surface not found"
}
}
guard terminalPanel.surface.liveSurfaceForGhosttyAccess(reason: "readTerminalTextBase64") != nil else {
return "ERROR: Terminal surface not found"
}

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 Unreachable guard after async-only startup request — requestReadDemandSurfaceStartIfNeeded() schedules surface creation inside a Task { @MainActor } and returns immediately; the surface is never synchronously available when control returns. Because this is a synchronous MainActor context with no await, the surface cannot change between the requestReadDemandSurfaceStartIfNeeded() call and the guard … != nil check on the next line — it will always be nil for a cold surface. The guard on line 5011 is therefore dead code; and the pre-existing guard on line 5015 is also unreachable in the warm path (already guarded by skipping the entire if block). Collapsing these to a single early-return after the startup request would remove the misleading synchronous-check appearance.

@azooz2003-bit
azooz2003-bit force-pushed the issue-helper-terminal-lifecycle branch 2 times, most recently from b493a4d to cce912b Compare June 16, 2026 05:12

@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

🤖 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
`@Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator`+Surface2.swift:
- Around line 315-320: The error message "Terminal surface is starting" in the
`.terminalNotReady` case of the ControlCommandCoordinator+Surface2.swift file is
a bare English string literal that must be localized. Replace this string with a
call to String(localized:defaultValue:) using an appropriate localization key,
then add matching entries for that key to the Resources/*.xcstrings file for all
supported locales, following the project's localization guidelines for
user-visible error messages.

In `@Sources/TerminalController.swift`:
- Around line 5015-5017: The guard statement checking
liveSurfaceForGhosttyAccess in the readTerminalTextBase64 method is redundant
because an earlier guard (lines 5011-5013) already validates that the surface is
non-nil and returns early if it fails. Remove the guard at lines 5015-5017 and
call liveSurfaceForGhosttyAccess directly without the nil-check, since execution
only reaches this point when the preflight check has already confirmed the
surface exists.
🪄 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

Run ID: 0adcab21-d4a2-4edd-8815-c66d68b7d116

📥 Commits

Reviewing files that changed from the base of the PR and between a947185 and 491e04e.

📒 Files selected for processing (8)
  • CLI/cmux.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceReadTextResolution.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift
  • Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController.swift
  • tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py

Comment thread Sources/TerminalController.swift Resolved
@azooz2003-bit
azooz2003-bit force-pushed the issue-helper-terminal-lifecycle branch from cce912b to 8451005 Compare June 16, 2026 05:18

@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 (1)
CLI/CMUXCLI+TmuxCompatSupport.swift (1)

143-147: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a shorter retry budget for OMX HUD probe calls.

This probe path now inherits the full readScreenPayload retry window (~6s), which can stall tmux-compat commands on cold surfaces even though this function already has a nil fallback. Please make retry policy configurable and pass a small budget (or zero retries) here.

Proposed change
--- a/CLI/CMUXCLI+ReadScreen.swift
+++ b/CLI/CMUXCLI+ReadScreen.swift
@@
-    func readScreenPayload(
+    func readScreenPayload(
         client: SocketClient,
-        params: [String: Any]
+        params: [String: Any],
+        maxRetries: Int = 60,
+        retryDelayMicros: useconds_t = 100_000
     ) throws -> [String: Any] {
         var readAttempt = 0
         while true {
             do {
                 return try client.sendV2(method: "surface.read_text", params: params)
             } catch let error as CLIError
-                where error.message.hasPrefix("terminal_not_ready:") && readAttempt < 60 {
+                where error.message.hasPrefix("terminal_not_ready:") && readAttempt < maxRetries {
                 readAttempt += 1
-                usleep(100_000)
+                usleep(retryDelayMicros)
             }
         }
     }
--- a/CLI/CMUXCLI+TmuxCompatSupport.swift
+++ b/CLI/CMUXCLI+TmuxCompatSupport.swift
@@
-        guard let payload = try? readScreenPayload(client: client, params: [
+        guard let payload = try? readScreenPayload(client: client, params: [
             "workspace_id": workspaceId,
             "surface_id": surfaceId,
             "lines": 4
-        ]),
+        ], maxRetries: 3),
             let text = payload["text"] as? String else {
             return nil
         }

As per coding guidelines, algorithmic-complexity.md requires flagging unbenchmarked/high-latency choices in production paths over user data, and this change introduces a fixed multi-second retry loop into a heuristic CLI path.

🤖 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 `@CLI/CMUXCLI`+TmuxCompatSupport.swift around lines 143 - 147, The
readScreenPayload function call in the OMX HUD probe path inherits the full
retry window (~6s) which can unnecessarily stall tmux-compat commands on cold
surfaces, despite the nil fallback already being in place. Make the retry policy
configurable in the readScreenPayload function signature by adding a parameter
for retry budget or retry count, then pass a small retry budget (or zero
retries) when calling readScreenPayload from this probe location to minimize
latency while maintaining the nil fallback behavior.

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 `@tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py`:
- Around line 119-122: The test at lines 119-122 validates success by checking
that error phrases are not present in the output, but this causes false failures
when terminal text legitimately contains those strings. Since the exit code
check at line 118 already enforces success, remove the substring checks for
"Terminal surface not found" and "Failed to read terminal text" from the _must
assertion and rely solely on the exit code validation for the success path.

---

Outside diff comments:
In `@CLI/CMUXCLI`+TmuxCompatSupport.swift:
- Around line 143-147: The readScreenPayload function call in the OMX HUD probe
path inherits the full retry window (~6s) which can unnecessarily stall
tmux-compat commands on cold surfaces, despite the nil fallback already being in
place. Make the retry policy configurable in the readScreenPayload function
signature by adding a parameter for retry budget or retry count, then pass a
small retry budget (or zero retries) when calling readScreenPayload from this
probe location to minimize latency while maintaining the nil fallback behavior.
🪄 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

Run ID: 64d725be-4873-4979-a191-803ecb9692f8

📥 Commits

Reviewing files that changed from the base of the PR and between 491e04e and cce912b.

📒 Files selected for processing (13)
  • CLI/CMUXCLI+ReadScreen.swift
  • CLI/CMUXCLI+TmuxCompatSupport.swift
  • CLI/cmux.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceReadTextResolution.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift
  • Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ComposerDictationTextMerge.swift
  • Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+TerminalReadDemand.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py
💤 Files with no reviewable changes (1)
  • Sources/TerminalController.swift

Comment on lines +119 to +122
_must(
"Terminal surface not found" not in output and "Failed to read terminal text" not in output,
f"{label}: read did not materialize {surface_ref}: {output!r}",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid payload substring checks for success-path terminal output.

At Line 120, the test treats successful stdout terminal content as an error signal by scanning for legacy error phrases. That can cause false failures when terminal text legitimately contains those strings. Line 118 already enforces success via exit code.

Suggested patch
 def _assert_cli_read_materializes(cli: str, args: List[str], surface_ref: str, label: str) -> None:
     code, output = _run_cli(
         cli,
         args,
         check=False,
         timeout=10.0,
     )
     _must(code == 0, f"{label}: read failed for {surface_ref}: {output!r}")
-    _must(
-        "Terminal surface not found" not in output and "Failed to read terminal text" not in output,
-        f"{label}: read did not materialize {surface_ref}: {output!r}",
-    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_must(
"Terminal surface not found" not in output and "Failed to read terminal text" not in output,
f"{label}: read did not materialize {surface_ref}: {output!r}",
)
def _assert_cli_read_materializes(cli: str, args: List[str], surface_ref: str, label: str) -> None:
code, output = _run_cli(
cli,
args,
check=False,
timeout=10.0,
)
_must(code == 0, f"{label}: read failed for {surface_ref}: {output!r}")
🤖 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 `@tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py`
around lines 119 - 122, The test at lines 119-122 validates success by checking
that error phrases are not present in the output, but this causes false failures
when terminal text legitimately contains those strings. Since the exit code
check at line 118 already enforces success, remove the substring checks for
"Terminal surface not found" and "Failed to read terminal text" from the _must
assertion and rely solely on the exit code validation for the success path.

@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

♻️ Duplicate comments (1)
Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift (1)

315-320: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Localize the newly introduced terminal_not_ready message text.

"Terminal surface is starting" is newly added protocol-visible text and is still a bare English literal. Route it through the project’s localization flow and add matching catalog entries for all supported locales.

As per coding guidelines, “All user-facing strings must be localized” and API/user-facing text changes must be localized across supported locales.

Suggested direction
-        case .terminalNotReady(let id):
+        case .terminalNotReady(let id):
             return .err(
                 code: "terminal_not_ready",
-                message: "Terminal surface is starting",
+                message: String(localized: "control.surface.readText.terminalNotReady",
+                                defaultValue: "Terminal surface is starting"),
                 data: .object(["surface_id": .string(id.uuidString)])
             )

Then add control.surface.readText.terminalNotReady to Resources/Localizable.xcstrings with translations for all supported locales.

🤖 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
`@Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator`+Surface2.swift
around lines 315 - 320, The message string "Terminal surface is starting" in the
terminalNotReady case of the error response is a user-facing string that must be
localized. Replace the hardcoded string literal with a localization key
reference using the project's localization system, then add the corresponding
entry to Resources/Localizable.xcstrings with translations for all supported
locales. The localization key should follow the naming convention suggested in
the comment (such as control.surface.readText.terminalNotReady) to maintain
consistency with the codebase's localization structure.

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 `@CLI/cmux.swift`:
- Around line 23027-23031: The contentSearchTerminalText function call on line
23030 is performing retry-heavy RPC work inside the per-workspace iteration
loop, causing O(workspaces × retry window) latency scaling as workspace count
grows. Fix this by first adding a short-circuit condition to skip the content
search when titleMatch is already true (since a title match is sufficient), then
refactor the contentSearchTerminalText implementation itself to use a bounded or
non-retrying read path instead of the current retryable operations, or
alternatively batch the content lookups across multiple workspaces to avoid
per-workspace RPC calls.

---

Duplicate comments:
In
`@Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator`+Surface2.swift:
- Around line 315-320: The message string "Terminal surface is starting" in the
terminalNotReady case of the error response is a user-facing string that must be
localized. Replace the hardcoded string literal with a localization key
reference using the project's localization system, then add the corresponding
entry to Resources/Localizable.xcstrings with translations for all supported
locales. The localization key should follow the naming convention suggested in
the comment (such as control.surface.readText.terminalNotReady) to maintain
consistency with the codebase's localization structure.
🪄 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

Run ID: 0cf1b30f-6102-4322-afbf-bdd5b8c0ab37

📥 Commits

Reviewing files that changed from the base of the PR and between cce912b and 8451005.

📒 Files selected for processing (13)
  • CLI/CMUXCLI+ReadScreen.swift
  • CLI/CMUXCLI+TmuxCompatSupport.swift
  • CLI/cmux.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceReadTextResolution.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift
  • Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ComposerDictationTextMerge.swift
  • Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+TerminalReadDemand.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py

Comment thread CLI/cmux.swift Resolved

@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

♻️ Duplicate comments (1)
CLI/cmux.swift (1)

23027-23031: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skip content-read RPC when title already matches in the workspace scan.

At Line 23029, contentSearchTerminalText(...) still runs even when titleMatch is already true, adding avoidable per-workspace read calls on scalable lists (≈1000 workspaces).

Suggested minimal fix
-                if includeContent && !query.isEmpty, let wsId = ws["id"] as? String {
+                if includeContent && !query.isEmpty && !titleMatch, let wsId = ws["id"] as? String {
                     let text = contentSearchTerminalText(workspaceId: wsId, client: client) ?? ""
                     contentMatch = text.localizedCaseInsensitiveContains(query)
                 }

As per coding guidelines, algorithmic-complexity.md requires avoiding per-target rescans/repeated expensive work in scalable workspace paths.

🤖 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 `@CLI/cmux.swift` around lines 23027 - 23031, The
contentSearchTerminalText(...) function call at line 23029 executes
unnecessarily when titleMatch is already true, causing avoidable per-workspace
read RPC calls. Add a !titleMatch check to the condition that guards the
contentSearchTerminalText invocation so that the expensive content search is
only performed when the title does not match the query.

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
`@Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift`:
- Around line 56-68: The test startIfNeededTrueIsForwarded only verifies
surfaceID and startIfNeeded parameters, while other similar tests verify all
relevant forwarded parameters including includeScrollback and lineLimit. Add
assertions for includeScrollback and lineLimit by adding `#expect` statements that
verify context.lastRead?.includeScrollback and context.lastRead?.lineLimit match
the expected values sent in the request to make the test more thorough and
consistent with the other parameter verification tests.

---

Duplicate comments:
In `@CLI/cmux.swift`:
- Around line 23027-23031: The contentSearchTerminalText(...) function call at
line 23029 executes unnecessarily when titleMatch is already true, causing
avoidable per-workspace read RPC calls. Add a !titleMatch check to the condition
that guards the contentSearchTerminalText invocation so that the expensive
content search is only performed when the title does not match the query.
🪄 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

Run ID: 2751911b-6e07-4164-9263-5c22d28d8bde

📥 Commits

Reviewing files that changed from the base of the PR and between 8451005 and dd23350.

📒 Files selected for processing (16)
  • CLI/CMUXCLI+ReadScreen.swift
  • CLI/CMUXCLI+TmuxCompatSupport.swift
  • CLI/cmux.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceContext.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceReadTextResolution.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/FakeSurfaceReadTextControlCommandContext.swift
  • Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ComposerDictationTextMerge.swift
  • Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+TerminalReadDemand.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py

Comment on lines +56 to +68
@Test func startIfNeededTrueIsForwarded() {
let (coordinator, context) = makeCoordinator()
let surfaceID = UUID()
context.readResolution = .terminalNotReady(surfaceID)

_ = coordinator.handle(request("surface.read_text", [
"surface_id": .string(surfaceID.uuidString),
"start_if_needed": .bool(true),
]))

#expect(context.lastRead?.surfaceID == surfaceID)
#expect(context.lastRead?.startIfNeeded == true)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider verifying all forwarded parameters for consistency.

Tests 1 and 2 verify all relevant forwarded parameters (surfaceID, includeScrollback, lineLimit, startIfNeeded), while this test only checks surfaceID and startIfNeeded. For consistency and more thorough verification, consider adding assertions for includeScrollback and lineLimit here as well.

📋 Suggested additions for consistency
     `#expect`(context.lastRead?.surfaceID == surfaceID)
+    `#expect`(context.lastRead?.includeScrollback == false)
+    `#expect`(context.lastRead?.lineLimit == nil)
     `#expect`(context.lastRead?.startIfNeeded == true)
🤖 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
`@Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift`
around lines 56 - 68, The test startIfNeededTrueIsForwarded only verifies
surfaceID and startIfNeeded parameters, while other similar tests verify all
relevant forwarded parameters including includeScrollback and lineLimit. Add
assertions for includeScrollback and lineLimit by adding `#expect` statements that
verify context.lastRead?.includeScrollback and context.lastRead?.lineLimit match
the expected values sent in the request to make the test more thorough and
consistent with the other parameter verification tests.

@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)
tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py (1)

119-122: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove brittle success-path substring filtering after exit-code success.

At Line 118 you already assert success (code == 0). The extra phrase filter at Lines 119-122 can produce false failures when terminal content legitimately contains those strings.

Suggested patch
 def _assert_cli_read_materializes(cli: str, args: List[str], surface_ref: str, label: str) -> None:
     code, output = _run_cli(
         cli,
         args,
         check=False,
         timeout=10.0,
     )
     _must(code == 0, f"{label}: read failed for {surface_ref}: {output!r}")
-    _must(
-        "Terminal surface not found" not in output and "Failed to read terminal text" not in output,
-        f"{label}: read did not materialize {surface_ref}: {output!r}",
-    )
🤖 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 `@tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py`
around lines 119 - 122, Remove the _must call that performs substring filtering
at lines 119-122 (the call checking that "Terminal surface not found" and
"Failed to read terminal text" are not in output). Since the exit code check at
line 118 already validates success with code == 0, this additional
substring-based validation is redundant and brittle, as it can produce false
failures when the terminal output legitimately contains those strings. The exit
code assertion is sufficient to confirm the operation succeeded.
🤖 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 `@tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py`:
- Around line 119-122: Remove the _must call that performs substring filtering
at lines 119-122 (the call checking that "Terminal surface not found" and
"Failed to read terminal text" are not in output). Since the exit code check at
line 118 already validates success with code == 0, this additional
substring-based validation is redundant and brittle, as it can produce false
failures when the terminal output legitimately contains those strings. The exit
code assertion is sufficient to confirm the operation succeeded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ef215dd-d162-450c-a0df-2cbdfa8cc9d1

📥 Commits

Reviewing files that changed from the base of the PR and between dd23350 and 20e704c.

📒 Files selected for processing (15)
  • CLI/CMUXCLI+ReadScreen.swift
  • CLI/CMUXCLI+TmuxCompatSupport.swift
  • CLI/cmux.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface2.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceContext.swift
  • Packages/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceReadTextResolution.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorSurfaceReadTextTests.swift
  • Packages/CmuxControlSocket/Tests/CmuxControlSocketTests/FakeSurfaceReadTextControlCommandContext.swift
  • Packages/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+BackgroundStartLifecycle.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+TerminalReadDemand.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • tests_v2/test_cli_background_terminal_helpers_read_screen_starts_pty.py

Comment on lines +17 to +37
let semaphore = DispatchSemaphore(value: 0)
let observer = NotificationCenter.default.addObserver(
forName: .terminalSurfaceDidBecomeReady,
object: nil,
queue: nil
) { notification in
guard notification.userInfo?["surfaceId"] as? UUID == surfaceID else { return }
semaphore.signal()
}
defer {
NotificationCenter.default.removeObserver(observer)
}

let secondResult = socketWorkerCoordinatorResult(for: request)
guard Self.retryableReadDemandSurfaceID(from: secondResult, request: request) == surfaceID else {
return surfaceReadTextSocketWorkerEncoder.response(id: request.id, secondResult)
}

guard semaphore.wait(timeout: .now() + 5) == .success else {
return surfaceReadTextSocketWorkerEncoder.response(id: request.id, secondResult)
}

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 DispatchSemaphore.wait blocks production socket worker thread

DispatchSemaphore is explicitly prohibited by the cmux-swift-blocking-runtime rule in production Swift. This call blocks the socket worker thread for up to 5 seconds waiting for terminalSurfaceDidBecomeReady. While moving the wait off the main actor is the right direction, using a semaphore here creates a blocking thread, which can exhaust the thread pool if multiple concurrent cold-start read requests arrive simultaneously. The correct shape is to declare this function async, replace v2MainSync with await-based main-actor hops, and replace the semaphore with withCheckedContinuation (or an AsyncChannel) listening on terminalSurfaceDidBecomeReady.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026
This was referenced Nov 2, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 70c1a293 Deployed Jun 16, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background workspaces have dead PTYs — read-screen/send-key fail with 'Surface not ready'

2 participants