Skip to content

Add --no-focus flag to cmux ssh - #2227

Merged
lawrencecchen merged 4 commits into
mainfrom
ssh-no-focus
Mar 27, 2026
Merged

lawrencecchen merged 4 commits into
mainfrom
ssh-no-focus

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add --no-focus flag to cmux ssh so scripted/background SSH workspace creation does not steal the user's active workspace focus.

Cherry-picked from #1942 by @maucher, rebased onto main with conflict resolution.

Changes

  • SSHCommandOptions: add noFocus: Bool field
  • parseSSHCommandOptions: parse --no-focus flag
  • runSSH: guard workspace.select call with if !sshOptions.noFocus
  • Help text: document --no-focus flag

Test plan

  • cmux ssh <host> --no-focus creates SSH workspace without switching focus
  • cmux ssh <host> (without flag) still switches focus as before
  • --no-focus alongside other flags (--name, --port) parses correctly

Summary by cubic

Adds a --no-focus flag to cmux ssh so scripts can create SSH workspaces without switching the active workspace. Default behavior remains unchanged.

  • New Features
    • Parse --no-focus into SSHCommandOptions.noFocus.
    • Skip workspace.select when --no-focus is set.
    • Update CLI help and top-level ssh synopsis to include the flag.

Written for commit 66e132d. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added a --no-focus option to the SSH command so users can create/configure a remote workspace without automatically selecting or focusing it.
  • Documentation

    • Updated SSH command help/usage to document the --no-focus option and its effect.

maucher added 2 commits March 26, 2026 18:41
When cmux ssh is used from a script, workspace.select is called
immediately after workspace.remote.configure, stealing the user's
active workspace focus before SSH is established.

Add --no-focus flag (consistent with break-pane/join-pane) to skip
the workspace.select call so the caller's workspace retains focus.
The caller can then redirect to the new workspace later via
cmux select-workspace.

Addresses cmux ssh case of #140; complementary to #1418.
- Fix existing test that constructs SSHCommandOptions directly
  (add noFocus: false to the initializer call)
- Make parseSSHCommandOptions internal so it's accessible from tests
- Add testParseSSHCommandOptionsNoFocusFlag covering:
  - flag sets noFocus=true, destination parses correctly
  - absent flag defaults noFocus=false
  - combined with --name and --port
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@vercel

vercel Bot commented Mar 27, 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 Mar 27, 2026 1:56am

@coderabbitai

coderabbitai Bot commented Mar 27, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a --no-focus flag to cmux ssh, parsed into SSHCommandOptions.noFocus, passed to runSSH, and used to conditionally skip the workspace.select call after workspace.remote.configure.

Changes

Cohort / File(s) Summary
SSH CLI
CLI/cmux.swift
Added noFocus: Bool to SSHCommandOptions, parse --no-focus into that field (default false), propagate through parseSSHCommandOptions and runSSH, conditionally skip workspace.select after workspace.remote.configure, and update cmux ssh help/usage text.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CMUX CLI
    participant Remote as workspace.remote.configure
    participant Selector as workspace.select

    CLI->>Remote: create/configure remote workspace (includes sshOptions)
    Remote-->>CLI: remote workspace created
    alt sshOptions.noFocus == false
        CLI->>Selector: workspace.select(new remote)
        Selector-->>CLI: selection confirmed
    else sshOptions.noFocus == true
        CLI-->>CLI: skip workspace.select
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 I hopped into code with a flag so light,

"--no-focus" I whispered, skip the select tonight,
No more leaps, the tunnel hums polite,
Quiet meadows, SSH stars bright. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding a --no-focus flag to the cmux ssh command.
Description check ✅ Passed The description covers the summary, changes, and test plan, but is missing the Testing, Demo Video, Review Trigger, and Checklist sections from the template.

✏️ 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 ssh-no-focus

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 Mar 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a --no-focus flag to cmux ssh so that scripted or background SSH workspace creation does not steal the user's active workspace focus. The implementation is clean and minimal: a noFocus: Bool field is added to SSHCommandOptions, the flag is parsed in parseSSHCommandOptions, and the workspace.select call in runSSH is guarded behind if !sshOptions.noFocus. Help text is also updated.\n\n- Core logic is correct — the guard around workspace.select is the right place to suppress focus, and the default (focus-switching) behavior is preserved unchanged.\n- One minor issue: parseSSHCommandOptions had its private access modifier removed, widening its visibility to internal with no corresponding new callers or tests. This appears to be an unintentional artifact of the cherry-pick/rebase from PR #1942 and should be reverted to private.\n- Help text placement is reasonable; --no-focus is grouped with the other cmux-specific flags.

Confidence Score: 5/5

Safe to merge — the only finding is a non-breaking style issue (dropped private modifier) with no runtime impact.

The feature is a straightforward additive flag with correct default behavior. The single P2 comment (access modifier widening) has zero runtime impact and can be addressed in a trivial follow-up. All existing cmux ssh paths are unaffected.

No files require special attention beyond the single-line access modifier fix in CLI/cmux.swift.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds noFocus: Bool to SSHCommandOptions, parses --no-focus flag, guards workspace.select call, and documents the flag in help text. One unintentional access-modifier widening (private dropped from parseSSHCommandOptions).

Sequence Diagram

sequenceDiagram
    participant User
    participant cmux CLI
    participant SocketClient
    participant cmux App

    User->>cmux CLI: cmux ssh user@host [--no-focus]
    cmux CLI->>cmux CLI: parseSSHCommandOptions()<br/>sets noFocus=true/false
    cmux CLI->>SocketClient: workspace.create
    SocketClient->>cmux App: workspace.create
    cmux App-->>SocketClient: workspaceId
    SocketClient-->>cmux CLI: workspaceId
    cmux CLI->>SocketClient: workspace.remote.configure
    SocketClient->>cmux App: workspace.remote.configure
    cmux App-->>SocketClient: configuredPayload
    SocketClient-->>cmux CLI: configuredPayload

    alt --no-focus NOT passed (default)
        cmux CLI->>SocketClient: workspace.select
        SocketClient->>cmux App: workspace.select (focus switches)
    else --no-focus passed
        cmux CLI->>cmux CLI: skip workspace.select (focus preserved)
    end

    cmux CLI->>User: OK workspace=... target=... state=...
Loading

Reviews (1): Last reviewed commit: "CLI: add test coverage for --no-focus fl..." | Re-trigger Greptile

Comment thread CLI/cmux.swift Outdated
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 1 file

@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/cmux.swift (1)

6409-6427: ⚠️ Potential issue | 🟡 Minor

Top-level cmux --help is still stale for ssh.

This subcommand help is updated, but Line 12620 in CMUXCLI.usage() still shows the old synopsis, so the new flag is invisible from the main help output.

📝 Proposed doc fix
-          ssh <destination> [--name <title>] [--port <n>] [--identity <path>] [--ssh-option <opt>] [-- <remote-command-args>]
+          ssh <destination> [--name <title>] [--port <n>] [--identity <path>] [--ssh-option <opt>] [--no-focus] [-- <remote-command-args>]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 6409 - 6427, Update the top-level help text in
CMUXCLI.usage() to match the revised ssh subcommand synopsis and flags: replace
the stale "ssh" usage entry with the new synopsis "cmux ssh <destination>
[flags] [-- <remote-command-args>]" and include the flags --name, --port,
--identity, --ssh-option, and --no-focus (and the brief descriptions) so the
main `cmux --help` shows the same options as the ssh subcommand help; look for
the "ssh" case or entry in CMUXCLI.usage() and make the text consistent with the
ssh help block in cmux.swift.
🧹 Nitpick comments (1)
CLI/cmux.swift (1)

3811-3899: Please add a runtime test for the --no-focus branch.

The parser coverage is useful, but tests_v2/test_ssh_remote_cli_metadata.py still only asserts that the new workspace becomes selected. There is no end-to-end assertion that cmux ssh ... --no-focus leaves current_workspace() unchanged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 3811 - 3899, The test suite is missing an
end-to-end runtime test that verifies the --no-focus flag leaves the selected
workspace unchanged; add a new test in tests_v2/test_ssh_remote_cli_metadata.py
that records the current_workspace() before running the CLI, invokes the ssh
command (using the same helpers already used in the file) with the --no-focus
flag and a destination, and then asserts current_workspace() is identical after
the call; target the behavior produced by parseSSHCommandOptions (function name)
by passing --no-focus and ensure the created SSHCommandOptions leads to no
change in selection (use existing CLI invocation helpers and current_workspace()
accessor in the test).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLI/cmux.swift`:
- Around line 3763-3768: The code currently calls
client.sendV2("workspace.select", params: selectParams) unless
sshOptions.noFocus is true, which makes focus the default; change this to make
focus opt-in by flipping the flag semantics: add or use an explicit
sshOptions.focus boolean (or invert sshOptions.noFocus to focus) and only call
client.sendV2(method: "workspace.select", params: selectParams) when that
explicit focus flag is true; update the option parsing/CLI flag for `cmux ssh`
to provide a `--focus` (default false) or invert the existing flag behavior, and
adjust any docs/tests that mention --no-focus accordingly so workspace.select is
only invoked when the user explicitly requests focus.

---

Outside diff comments:
In `@CLI/cmux.swift`:
- Around line 6409-6427: Update the top-level help text in CMUXCLI.usage() to
match the revised ssh subcommand synopsis and flags: replace the stale "ssh"
usage entry with the new synopsis "cmux ssh <destination> [flags] [--
<remote-command-args>]" and include the flags --name, --port, --identity,
--ssh-option, and --no-focus (and the brief descriptions) so the main `cmux
--help` shows the same options as the ssh subcommand help; look for the "ssh"
case or entry in CMUXCLI.usage() and make the text consistent with the ssh help
block in cmux.swift.

---

Nitpick comments:
In `@CLI/cmux.swift`:
- Around line 3811-3899: The test suite is missing an end-to-end runtime test
that verifies the --no-focus flag leaves the selected workspace unchanged; add a
new test in tests_v2/test_ssh_remote_cli_metadata.py that records the
current_workspace() before running the CLI, invokes the ssh command (using the
same helpers already used in the file) with the --no-focus flag and a
destination, and then asserts current_workspace() is identical after the call;
target the behavior produced by parseSSHCommandOptions (function name) by
passing --no-focus and ensure the created SSHCommandOptions leads to no change
in selection (use existing CLI invocation helpers and current_workspace()
accessor in the test).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b24017f2-deb2-4750-88e1-d864a2867b16

📥 Commits

Reviewing files that changed from the base of the PR and between ccd84bd and 2f7b495.

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

Comment thread CLI/cmux.swift
Comment on lines 3763 to +3768
// `cmux ssh` is an explicit "open this remote workspace now" action,
// so we intentionally select the newly created workspace after wiring
// up the remote connection.
_ = try client.sendV2(method: "workspace.select", params: selectParams)
// up the remote connection — unless --no-focus is passed.
if !sshOptions.noFocus {
_ = try client.sendV2(method: "workspace.select", params: selectParams)
}

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 | 🟠 Major

Make focus opt-in here, not opt-out.

cmux ssh still calls workspace.select by default, so scripted SSH workspace creation continues to steal selection unless callers know to pass --no-focus. For a non-focus command, the safe behavior should be the default and any focus change should be explicit.

Based on learnings: Socket/CLI commands must not steal macOS app focus; only explicit focus-intent commands may mutate in-app focus/selection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 3763 - 3768, The code currently calls
client.sendV2("workspace.select", params: selectParams) unless
sshOptions.noFocus is true, which makes focus the default; change this to make
focus opt-in by flipping the flag semantics: add or use an explicit
sshOptions.focus boolean (or invert sshOptions.noFocus to focus) and only call
client.sendV2(method: "workspace.select", params: selectParams) when that
explicit focus flag is true; update the option parsing/CLI flag for `cmux ssh`
to provide a `--focus` (default false) or invert the existing flag behavior, and
adjust any docs/tests that mention --no-focus accordingly so workspace.select is
only invoked when the user explicitly requests focus.

@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 the current code and only fix it if needed.

Inline comments:
In `@CLI/cmux.swift`:
- Line 6421: The top-level help synopsis is missing the new --no-focus flag
added to the ssh subcommand; update the help text generator so the synopsis for
the ssh subcommand matches subcommandUsage("ssh"). Locate the top-level
usage/summary renderer (the help printing function or constant that builds the
main synopsis string in CLI/cmux.swift) and add the --no-focus flag to the ssh
entry so that both cmux help and cmux ssh --help show identical options.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 84c133a9-1843-4363-948e-87dac47eb93a

📥 Commits

Reviewing files that changed from the base of the PR and between 2f7b495 and 9238ebd.

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

Comment thread CLI/cmux.swift

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

3763-3768: ⚠️ Potential issue | 🟠 Major

Make SSH workspace selection explicit, not default.

cmux ssh still calls workspace.select by default (Line 3767), so focus/selection changes happen unless callers know to pass --no-focus. This still violates the non-focus-command policy.

♻️ Proposed direction
- // ... unless --no-focus is passed.
- if !sshOptions.noFocus {
+ // Only mutate selection when explicitly requested.
+ if sshOptions.focus {
     _ = try client.sendV2(method: "workspace.select", params: selectParams)
 }

Then parse and document an explicit --focus flag (default false), and treat --no-focus as legacy compatibility if needed.

Based on learnings: Socket/CLI commands must not steal macOS app focus; only explicit focus-intent commands may mutate in-app focus/selection (window.focus, workspace.select/next/previous/last, surface.focus, pane.focus/last, etc.), and non-focus commands should preserve current user focus context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 3763 - 3768, The CLI currently calls
client.sendV2(method: "workspace.select", ...) whenever !sshOptions.noFocus,
which still mutates app focus by default; change this to only call
workspace.select when an explicit --focus flag is passed (e.g., add
sshOptions.focus: Bool default false and use if sshOptions.focus { _ = try
client.sendV2(method: "workspace.select", params: selectParams) }); keep
--no-focus as a legacy alias mapping to focus = false if you must, and update
the CLI flag parsing and help/docs to document the new --focus explicit intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@CLI/cmux.swift`:
- Around line 3763-3768: The CLI currently calls client.sendV2(method:
"workspace.select", ...) whenever !sshOptions.noFocus, which still mutates app
focus by default; change this to only call workspace.select when an explicit
--focus flag is passed (e.g., add sshOptions.focus: Bool default false and use
if sshOptions.focus { _ = try client.sendV2(method: "workspace.select", params:
selectParams) }); keep --no-focus as a legacy alias mapping to focus = false if
you must, and update the CLI flag parsing and help/docs to document the new
--focus explicit intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7da74c91-7470-4828-88fd-f6ba82f6d021

📥 Commits

Reviewing files that changed from the base of the PR and between 9238ebd and 66e132d.

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

@lawrencecchen
lawrencecchen merged commit 30cb371 into main Mar 27, 2026
15 checks passed
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* CLI: add --no-focus flag to cmux ssh

When cmux ssh is used from a script, workspace.select is called
immediately after workspace.remote.configure, stealing the user's
active workspace focus before SSH is established.

Add --no-focus flag (consistent with break-pane/join-pane) to skip
the workspace.select call so the caller's workspace retains focus.
The caller can then redirect to the new workspace later via
cmux select-workspace.

Addresses cmux ssh case of manaflow-ai#140; complementary to manaflow-ai#1418.

* CLI: add test coverage for --no-focus flag parsing

- Fix existing test that constructs SSHCommandOptions directly
  (add noFocus: false to the initializer call)
- Make parseSSHCommandOptions internal so it's accessible from tests
- Add testParseSSHCommandOptionsNoFocusFlag covering:
  - flag sets noFocus=true, destination parses correctly
  - absent flag defaults noFocus=false
  - combined with --name and --port

* Update CLI/cmux.swift

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Add --no-focus to top-level ssh help synopsis

---------

Co-authored-by: Łukasz Majcher <lukasz.majcher@samsara.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 66e132d7 Deployed Mar 27, 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.

2 participants