Skip to content

Fix explicit surface routing for read-screen and send - #6605

Merged
austinywang merged 2 commits into
mainfrom
issue-6598-tui-readscreen-send
Jun 22, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-6598-tui-readscreen-send

Conversation

@austinywang

@austinywang austinywang commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Stop read-screen, send, send-key, and panel send aliases from inheriting the caller workspace when an explicit surface ref/UUID is supplied.
  • Keep caller-workspace fallback for omitted surfaces and numeric surface indexes, where workspace scope is needed.
  • Add a regression test for explicit surface:N CLI routing from a caller surface with ambient CMUX_WORKSPACE_ID.

Reproduction

  • Reproduced locally before the fix: cmux read-screen --surface surface:11 --lines 5 failed with invalid_params: Surface is not a terminal when run from another workspace.
  • cmux read-screen --workspace workspace:3 --surface surface:11 --lines 5 succeeded, confirming the explicit surface was being scoped to the wrong caller workspace.
  • cmux send --surface surface:11 " " hit the same invalid terminal error.

Verification

  • Not run: local tests and local builds per issue instructions; CI is the validation gate for this PR.

Fixes #6598


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fix incorrect CLI routing when an explicit surface or panel ref/UUID is provided. read-screen, capture-pane, pipe-pane, send, send-key, send-panel, and send-key-panel now route globally to the target instead of inheriting the caller workspace, fixing #6598.

  • Bug Fixes
    • Scope workspace_id only for numeric or omitted handles; for explicit refs/UUIDs, omit workspace_id and window_id in RPCs.
    • Resolve numeric handles via surface.list within the caller workspace; do not pass window_id to surface.list.
    • Added tests for all seven commands with explicit refs, plus a numeric-index path test on read-screen.

Written for commit 395029f. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved CLI routing for explicit surface and panel commands so workspace/window parameters are included only when appropriate, matching expected behavior for string vs numeric surface handles.
    • Adjusted workspace-argument logic to validate panel input before computing workspace routing, and tightened rules around whether the current workspace may be used.
  • Tests

    • Added a new serialized CLI routing test suite that uses a mock Unix-socket server to verify outgoing requests for read-screen, send, send-key, send-panel, and send-key-panel.

@vercel

vercel Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 22, 2026 7:53pm
cmux-staging Building Building Preview, Comment Jun 22, 2026 7:53pm

@coderabbitai

coderabbitai Bot commented Jun 22, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ccfb578-9b44-42f2-880f-bb4a46f294b5

📥 Commits

Reviewing files that changed from the base of the PR and between ab50e06 and 395029f.

📒 Files selected for processing (1)
  • CLI/cmux.swift

📝 Walkthrough

Walkthrough

Seven CLI subcommand parsing paths now delegate workspace-inheritance logic to a new private helper callerWorkspaceForSurfaceHandle(_:windowRaw:), which returns CMUX_WORKSPACE_ID only when windowRaw is nil and the surface/panel handle is blank or numeric. A new integration test suite validates routing behavior over a mock Unix socket for both string and numeric surface references.

Changes

Explicit surface workspace inheritance fix and validation

Layer / File(s) Summary
callerWorkspaceForSurfaceHandle helper and call site refactor
CLI/cmux.swift
Adds the private static helper that returns CMUX_WORKSPACE_ID only when windowRaw is nil and the handle is blank or numeric, then replaces the old inline windowRaw == nil ? CMUX_WORKSPACE_ID : nil rule at seven call sites (three --surface parsers, send-panel, send-key-panel, and two additional command blocks); reorders the --panel required-argument guard to run before workspaceArg computation; updates normalizeWorkspaceHandle call sites with new allowCurrentWorkspace logic that checks both winId and handle format.
Test infrastructure: socket binding, mock server, and process execution
cmuxTests/CLIExplicitSurfaceRoutingTests.swift
Adds ServerState (thread-safe request recorder via NSLock), ProcessRunResult (exit status and captured streams), bindUnixSocket (sockaddr_un bind/listen with path validation), startMockServer (background accept/read loop with newline framing and method dispatch), JSON response/parsing helpers, and runProcess (pipe-captured CLI launcher with semaphore timeout and SIGKILL fallback).
Integration test cases and Xcode project registration
cmuxTests/CLIExplicitSurfaceRoutingTests.swift, cmux.xcodeproj/project.pbxproj
Adds explicitSurfaceCommandsDoNotInheritCallerWorkspace (string ref suppresses workspace_id/window_id across five subcommands), numericSurfaceHandleStillInheritsCallerWorkspaceForIndexResolution (numeric handle includes workspace_id and resolves correctly), and the reusable assertExplicitSurfaceCommand helper. Registers the test file in Xcode project via PBXBuildFile, PBXFileReference, group child, and PBXSourcesBuildPhase entries.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Integration Test
    participant Socket as Unix Socket
    participant Server as Mock Server
    participant CLI as cmux CLI

    rect rgba(100, 149, 237, 0.5)
      Note over Test,CLI: String surface ref (surface:11)
      Test->>Socket: bind temp socket
      Test->>Server: start accept loop
      Test->>CLI: spawn with --surface surface:11, CMUX_WORKSPACE_ID set
      CLI->>Socket: connect and send JSON-RPC request
      Server->>Server: record request (no workspace_id, no window_id)
      Server-->>CLI: v2Response with surface_id
      CLI-->>Test: exit 0
      Test->>Server: assert workspace_id absent, surface_id == surface:11
    end

    rect rgba(144, 238, 144, 0.5)
      Note over Test,CLI: Numeric surface handle (--surface 5)
      Test->>Socket: bind temp socket
      Test->>Server: start accept loop (handles surface.list and surface.read_text)
      Test->>CLI: spawn with --surface 5, CMUX_WORKSPACE_ID set
      CLI->>Socket: surface.list request (workspace_id present)
      Server-->>CLI: list response with UUID entry
      CLI->>Socket: surface.read_text request (workspace_id present)
      Server-->>CLI: read_text response
      CLI-->>Test: exit 0
      Test->>Server: assert workspace_id present, surface_id == expected UUID
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#4562: Both PRs ensure cmuxTests/*.swift files are correctly wired into cmux.xcodeproj/project.pbxproj's build phases (this PR registers CLIExplicitSurfaceRoutingTests.swift; related PR adds CI lint and other test source wiring).

Suggested reviewers

  • lawrencecchen

Poem

🐇 A surface by name needs no workspace to chase,
But a number must borrow the caller's home base.
The helper now trims, checks blank or pure int,
Seven call sites refactored — clean logic, no squint.
The mock server listens, the tests assert true,
Hopping through sockets, the rabbit approves you! 🎉

🚥 Pre-merge checks | ✅ 22 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: fixing explicit surface routing for read-screen and send commands.
Description check ✅ Passed The description covers all required template sections with sufficient detail: summary explains what changed and why, testing section notes CI validation, and checklist is present.
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 Production code changes are minimal (+13/-13 lines) and introduce a pure static function with no new types or isolation markers. Test file properly uses @unchecked Sendable with NSLock protection....
Cmux Swift Blocking Runtime ✅ Passed Production code (CLI/cmux.swift) introduces no blocking primitives; only non-blocking workspace resolution logic. Blocking primitives (DispatchSemaphore, NSLock) appear only in test-only scaffoldin...
Cmux Expensive Synchronous Load ✅ Passed PR adds lightweight helper callerWorkspaceForSurfaceHandle (string trimming + O(1) env dict lookup) to CLI command dispatcher, not socket handlers/UI/@mainactor paths. Test file is production-agnos...
Cmux Cache Substitution Correctness ✅ Passed The PR uses ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"], which is a fresh read of OS environment variables, not a cached value. No persistence/history/undo/snapshot paths are involved;...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift code and project configuration; rule scope explicitly excludes Swift ("covered by swift-blocking-runtime.md"). No TypeScript, JavaScript, or shell script changes present.
Cmux Algorithmic Complexity ✅ Passed The PR introduces no algorithmic complexity violations. New functions avoid collection scans; normalizeSurfaceHandle is called once per command, not in loops; numeric index lookup is single-pass O(...
Cmux Swift Concurrency ✅ Passed Test-only DispatchQueue patterns in CLIExplicitSurfaceRoutingTests.swift are allowed per modernization rules; CLI/cmux.swift adds only synchronous logic with no new async patterns introduced.
Cmux Swift @Concurrent ✅ Passed The PR introduces no @concurrent annotation violations. All added Swift code is synchronous or uses standard DispatchQueue patterns correctly.
Cmux Swift File And Package Boundaries ✅ Passed PR passes Swift file/package boundaries check. CLI/cmux.swift has only ±13 line refactoring to existing 34k-line file. New test file CLIExplicitSurfaceRoutingTests.swift is 392 lines (under 400-lin...
Cmux Swiftpm Lockfiles ✅ Passed Initial commit includes all required Package.resolved lockfiles: 10 package-local files for cmux-owned packages and root Xcode workspace lockfile. No .gitignore violations found, and Xcode project...
Cmux Swift Logging ✅ Passed No logging violations found. Production code changes in CLI/cmux.swift contain no print/debugPrint/dump/NSLog statements, no file logging, and no secrets exposure. Test file is exempt from restrict...
Cmux User-Facing Error Privacy ✅ Passed PR changes comply with user-facing-errors.md. The new callerWorkspaceForSurfaceHandle() helper is private; environment variables are used only internally and not exposed to users. Error messages re...
Cmux Full Internationalization ✅ Passed PR introduces only logic changes for workspace resolution in CLI parsing and a test file; no new user-facing strings or materialized text changes that require localization.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI code—only CLI argument parsing, unit tests, and project configuration. SwiftUI state layout rules are not applicable.
Cmux Architecture Rethink ✅ Passed Changes are a small correctness fix with clear owner and invariant: callerWorkspaceForSurfaceHandle() returns workspace only for numeric/omitted handles. Test-only synchronization (NSLock, Dispatch...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains only CLI parsing changes and test-only fixtures; no new user-visible NSWindow, NSPanel, NSWindowController, Window, or WindowGroup code that would require cmux.* identifiers and cmuxAux...
Cmux Source Artifacts ✅ Passed All three changed files (CLI/cmux.swift, project.pbxproj, CLIExplicitSurfaceRoutingTests.swift) are intentional source code, configs, or test suites with no artifacts, caches, or generated output v...
Cmux No Test Or Debug Seam In Production Source ✅ Passed The custom check applies to production source under /Sources/ paths. The modified file (CLI/cmux.swift) is located at the top level and does not match this scope. Additionally, the new helper f...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6598-tui-readscreen-send

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 22, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a routing bug where read-screen, send, send-key, capture-pane, pipe-pane, and the two panel-send aliases inherited CMUX_WORKSPACE_ID from the caller environment even when an explicit surface ref or UUID was supplied, causing "Surface is not a terminal" errors across workspace boundaries.

  • Introduces callerWorkspaceForSurfaceHandle, a static helper that returns CMUX_WORKSPACE_ID only for absent or purely numeric surface handles, and nil for any explicit ref or UUID — paired with normalizeWorkspaceHandle's existing allowCurrent: false default, this correctly suppresses workspace scoping for the explicit-ref path.
  • Removes a duplicate window_id assignment inside the numeric-index surface.list path.
  • Adds a serialized integration test suite (CLIExplicitSurfaceRoutingTests) that spawns a real CLI subprocess against a mock Unix socket, verifying both the "explicit ref must not inherit caller workspace" invariant and the "numeric index must still scope to caller workspace" positive control across all seven affected subcommands.

Confidence Score: 5/5

The fix is safe to merge: it narrows the workspace-inheritance path to only the cases that genuinely need it (absent or numeric surface handles) and leaves the explicit-ref and UUID paths completely unscoped, matching the intended API contract.

The helper callerWorkspaceForSurfaceHandle correctly gates workspace inheritance on a simple numeric/empty check, and normalizeWorkspaceHandle defaults to allowCurrent: false, so nil workspace arguments propagate as nil without any RPC fallback. The new integration tests spawn real CLI subprocesses and verify both sides of the contract — explicit refs route globally, numeric indexes remain scoped — for all seven affected subcommands.

The inline allowCurrentWorkspace expression in the pipe-pane branch of CLI/cmux.swift silently duplicates the classification logic from callerWorkspaceForSurfaceHandle and would need a manual update if the helper's definition changes.

Important Files Changed

Filename Overview
CLI/cmux.swift Introduces callerWorkspaceForSurfaceHandle to suppress caller-workspace inheritance for explicit surface refs/UUIDs; also removes a duplicate window_id assignment in the numeric-index surface.list path. The pipe-pane branch adds an inline allowCurrentWorkspace expression that re-implements the same classification.
cmuxTests/CLIExplicitSurfaceRoutingTests.swift New serialized integration test suite spawning a real CLI subprocess against a mock Unix-socket server; covers all seven affected commands for the explicit-ref invariant and the numeric-index positive control.
cmux.xcodeproj/project.pbxproj Adds CLIExplicitSurfaceRoutingTests.swift to the Xcode test target sources and file references; mechanical project-file update with no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["CLI command invoked\n(read-screen / send / send-key /\ncapture-pane / pipe-pane /\nsend-panel / send-key-panel)"] --> B{explicit --workspace\nor --window given?}
    B -- yes --> C["Use supplied workspace/window\n(unchanged path)"]
    B -- no --> D["callerWorkspaceForSurfaceHandle\n(sfArg, windowRaw)"]
    D --> E{surface arg is\nempty or Int?}
    E -- "yes / nil" --> F["workspaceArg = CMUX_WORKSPACE_ID\n(inherit caller workspace)"]
    E -- "no / explicit ref or UUID" --> G["workspaceArg = nil\n(no workspace scoping)"]
    F --> H["normalizeWorkspaceHandle\nreturns callerWorkspaceId"]
    G --> I["normalizeWorkspaceHandle\nreturns nil\n(allowCurrent defaults false)"]
    H --> J{surface arg\nis numeric?}
    J -- yes --> K["surface.list scoped to\ncaller workspace\n→ resolve surface_id"]
    J -- no --> L["surface ref passed directly\nwith workspace_id"]
    I --> M["surface ref or UUID passed\nwith no workspace_id\nor window_id"]
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"}}}%%
flowchart TD
    A["CLI command invoked\n(read-screen / send / send-key /\ncapture-pane / pipe-pane /\nsend-panel / send-key-panel)"] --> B{explicit --workspace\nor --window given?}
    B -- yes --> C["Use supplied workspace/window\n(unchanged path)"]
    B -- no --> D["callerWorkspaceForSurfaceHandle\n(sfArg, windowRaw)"]
    D --> E{surface arg is\nempty or Int?}
    E -- "yes / nil" --> F["workspaceArg = CMUX_WORKSPACE_ID\n(inherit caller workspace)"]
    E -- "no / explicit ref or UUID" --> G["workspaceArg = nil\n(no workspace scoping)"]
    F --> H["normalizeWorkspaceHandle\nreturns callerWorkspaceId"]
    G --> I["normalizeWorkspaceHandle\nreturns nil\n(allowCurrent defaults false)"]
    H --> J{surface arg\nis numeric?}
    J -- yes --> K["surface.list scoped to\ncaller workspace\n→ resolve surface_id"]
    J -- no --> L["surface ref passed directly\nwith workspace_id"]
    I --> M["surface ref or UUID passed\nwith no workspace_id\nor window_id"]
Loading

Reviews (5): Last reviewed commit: "fix: route explicit surface CLI I/O glob..." | Re-trigger Greptile

Comment on lines 6348 to 6369
let read = try runCase(
name: "read-explicit-surface",
arguments: ["read-screen", "--surface", targetSurfaceRef, "--lines", "5"],
expectedMethod: "surface.read_text"
)
XCTAssertFalse(read.timedOut, read.stderr)
XCTAssertEqual(read.status, 0, read.stderr)
XCTAssertEqual(read.stdout, "agent screen\n\n")

let send = try runCase(
name: "send-explicit-surface",
arguments: ["send", "--surface", targetSurfaceRef, "hello"],
expectedMethod: "surface.send_text",
expectedText: "hello"
)
XCTAssertFalse(send.timedOut, send.stderr)
XCTAssertEqual(send.status, 0, send.stderr)
XCTAssertTrue(send.stdout.contains("OK"), send.stdout)
}

func testPipePaneWindowWorkspaceOmittedSurfaceDoesNotUseSelectedWorkspaceSurface() throws {
let cliPath = try bundledCLIPath()

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 Regression test omits send-key, send-panel, and send-key-panel

All five subcommands received the same workspace-inheritance fix, but the new regression test only exercises read-screen and send. send-key, send-panel, and send-key-panel have no coverage for the "explicit surface/panel ref must not inherit CMUX_WORKSPACE_ID" invariant. A future refactor that accidentally reverts those three cases would go undetected by CI. The test also doesn't include a positive control verifying that a bare numeric surface index (e.g. "5") still does inherit the caller workspace — the two-sided contract is only half-verified.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Expanded the regression to cover send-key, send-panel, send-key-panel, and the numeric-index positive control. The test now verifies explicit surface refs skip caller workspace while bare numeric indexes still inherit it.

— Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in the current head: CLIExplicitSurfaceRoutingTests now covers read-screen, send, send-key, send-panel, send-key-panel, capture-pane, pipe-pane, plus the numeric surface index positive control.

— Claude Code

@austinywang
austinywang force-pushed the issue-6598-tui-readscreen-send branch from b9ebed5 to ab50e06 Compare June 22, 2026 19:38

This branch was successfully deployed

1 active deployment
Preview – cmux — 395029f4 Deployed Jun 22, 2026 by vercel[bot]
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.

read-screen and send fail on TUI agent surfaces (Claude Code, Codex, Hermes) — blocks multi-agent orchestration

1 participant