Skip to content

cli: bootstrap the CodeRouter CLI when cmux cr finds none - #12144

Merged
austinywang merged 3 commits into
mainfrom
issue-12139-cmux-cr-coderouter-bootstrap
Sep 8, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-12139-cmux-cr-coderouter-bootstrap

Conversation

@austinywang

@austinywang austinywang commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #12139

Problem

cmux cr ... (and every cmux coderouter ... verb cmux does not own) exec'd whatever coderouter/cr PATH offered and exited 127 otherwise. On a machine where CodeRouter was never installed, the alias cmux advertises in --help and docs/cli-contract.md was dead on arrival, and the generic "Required CLI not found" message did not even say what to install.

What changes

The passthrough now has one owner, CLI/CMUXCLI+CoderouterPassthrough.swift (300 lines), which handles resolve → bootstrap → exec. CLI/cmux.swift loses the moved code (net −86 lines) and two of its private helpers (resolveExecutableInPath, cliDebugLog) become internal so the new file can reuse them.

  • Resolution is PATH coderouter, PATH cr (unchanged), then the official installer's own target: $CODEROUTER_INSTALL/bin/coderouter, default ~/.coderouter/bin/coderouter, computed exactly as install.sh computes it. That covers the second class of failure behind this issue: an install whose shell-profile PATH line has not reached the calling process (a terminal opened before the install, a hook, a script). No hit makes a network call.
  • Bootstrap runs only when nothing resolves and stdin and stderr are both terminals. cmux prints the documented command verbatim, says what it does (checksum-verified binary into ~/.coderouter/bin, PATH line appended to the shell profile), and asks once:
    CodeRouter CLI is not installed. cmux can install it now by running the official installer:
      curl -fsSL https://cmux.com/coderouter/install.sh | sh
    This downloads the checksum-verified CodeRouter binary into /Users/you/.coderouter/bin and adds that directory to your shell PATH.
    Install CodeRouter now? [y/N]
    
    After y, cmux fetches the script with /usr/bin/curl --proto =https --tlsv1.2 -fsSL --max-time 60 into a private 0700 temp dir, runs it with /bin/sh as a child on the same terminal, then re-resolves and execves the new install with the original arguments.
  • Every other outcome exits 127 with the exact install command on stderr: non-interactive (Error: CodeRouter CLI is not installed. Install it, then retry: + the command), declined, curl failure (curl's own stderr line first, then cmux's summary with curl's exit status), installer failure (installer's own stderr first, then cmux's line with its exit status).
  • The child environment for curl, the installer, and CodeRouter itself is the same CMUX_*/CMUXD_*-stripped one as before; the cmux socket is never opened on this path.
  • cmux coderouter status|machines|claude (cmux-owned verbs over the app socket) are untouched.

Trade-offs, stated

  1. Official installer script, not a native downloader or a bundled binary. Running the documented script means cmux installs CodeRouter exactly the way the docs do, and the web team stays the single owner of artifact names, manifest format, checksum verification, the cr symlink, and the profile PATH line. A Swift re-implementation of the download would duplicate that release-layout knowledge in a second place that drifts silently, and it would need either a version pin or a manifest fetch cmux does not own. Bundling the binary with the app would tie CodeRouter's release cadence to cmux's and add signing work. The cost: the bootstrap depends on https://cmux.com/coderouter/install.sh staying the stable entry point (it already is the documented one) and on /usr/bin/curl and /bin/sh, both of which macOS always ships; the script itself refuses to run without curl anyway.
  2. The documented command is shown verbatim but run as two steps. A literal curl … | sh returns sh's status (0 for an empty script when curl fails without pipefail) and can hand a truncated download to the shell. Fetch-then-run gives honest exit statuses for both halves and never executes a partial script. The user still sees the exact command they can run by hand.
  3. Install location is the installer's own (~/.coderouter/bin), not ~/.cmux/bin. Plain cr then works in the user's shells after the installer's PATH line, a later manual install does not create a second copy, and cmux keeps finding it without PATH. The issue floated ~/.cmux/bin; that would have made cr cmux-only.
  4. Confirmation is never skippable, and there is no auto-install for non-interactive callers. Executing remote code silently was the line I did not cross; a script or agent gets the exact command and exit 127 and can run it itself. If a CMUX_CODEROUTER_AUTO_INSTALL=1 opt-in is wanted later it is a one-line addition, but I did not add an unrequested surface.
  5. Exit 127 everywhere CodeRouter did not run, including decline and installer failure, rather than propagating the installer's status: cmux cr X never ran X, and scripts already handle 127 as "command not found". The reason is always on stderr.
  6. The generic "Required CLI not found" message is replaced, and CodeRouter is now named. PR Add cmux CodeRouter CLI passthrough aliases #10109 deliberately kept the message generic and its test asserted that "coderouter" never appeared in stderr. The issue explicitly asks for the exact install command, which names the product, so that test moves into the new suite with the new contract; the secrecy assertions that still matter (no socket path, capability, password, or filesystem path in stderr) are kept.
  7. Debug-only test seam. CMUX_CODEROUTER_INSTALLER_SCRIPT (compiled only under DEBUG, like cliDebugLog) points the bootstrap at a local stand-in for install.sh so the tests exercise offer → confirm → install → re-resolve → exec without the network. Release builds have no override. The real fetch is covered by the manual verification below, not by a unit test.
  8. Deprioritized edge case, decided: not handled. An unrelated binary named cr or coderouter on PATH is still exec'd. Detecting it would need a probe spawn (for example --version) on every invocation, which contradicts "existing installs are exec'd unchanged" and adds latency to every call, or a signature check that would break on the npm-distributed CodeRouter (a Node script, which is what Austin's own machine has at ~/.local/bin/coderouter). Austin marked it probably not worth it; recorded here.
  9. Help line wording changed in en and ja only. cli.coderouter.aliases now reads "(aliases for the CodeRouter CLI; offers to install it when missing)"; the other 18 locales keep their old text and are marked needs_review for the release translation pass, per the localization workflow. The unused cli.coderouter.error.notFound key is removed from the catalog.

Before / after

Before, shipped 0.64.22 (Austin's daily driver), restricted PATH plus a temporary HOME, exactly the issue's command:

$ cmux version
cmux 0.64.22 (102) [ddd4a01bc]
$ env PATH=/usr/bin:/bin /Applications/cmux.app/Contents/Resources/bin/cmux cr --version; echo "exit=$?"
Error: Unknown command 'cr'. Run 'cmux --help' for the full command list.
exit=2

The shipped build predates the alias entirely (PR #10109 is not in 0.64.22), so on the daily driver cmux cr is not even 127. The exit-127 behavior in the issue is main; the failing-test commit dfd801a9b1 shows it in CI (run linked below).

After, tagged Debug build of this branch: pending, filled in below once the queued cloud build lands.

Checks run

  • Two-commit regression policy: dfd801a9b1 adds the tests only, 1c3541e1fa the fix. Focused test-e2e.yml runs (cmux-unit scheme, so the whole test target compiles):
  • The full ci.yml is path-filtered and does not run for CLI changes on pull requests (ci-status comes from the fallback). A manual dispatch on the merged HEAD (https://github.com/manaflow-ai/cmux/actions/runs/34219195504) could not reach the Swift jobs: web-typecheck fails on main's own scripts/check-devbox-image-reachable.ts(148,17): error TS2339: Property 'main' does not exist on type 'ImportMeta' (from Check that the shipped devbox image is reachable, only when the pair changed #12132), and linux-preflight gates every Swift job on it. Nothing in this PR touches web/.
  • python3 scripts/normalize-pbxproj.py + ./scripts/check-pbxproj.sh: ok. ./scripts/lint-pbxproj-test-wiring.sh: ok (800 test files).
  • xcrun swiftc -parse on every touched Swift file: clean (no local build; the tagged build is the compile check).
  • No .github/swift-warning-budget.tsv change; the new file uses no pattern from the budget.
  • Localization audit: new user-facing strings are the eight cli.coderouter.bootstrap.* keys plus the cli.coderouter.aliases help line, all with en and ja values in Resources/Localizable.xcstrings and resolved through the existing CMUXDiffViewerLocalization path (the ja --help test still passes with the new text). docs/cli-contract.md has no localized variant. The one remaining bare-English surface is the pre-existing unlocalized coderouterUsage block in CLI/CMUXCLI+Coderouter.swift, where I only reworded one sentence; localizing that block is out of scope for this issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GrWb19oD1CAyTEPLjiJpe9


Note

Medium Risk
Interactive bootstrap downloads and executes a remote installer script (user-confirmed only), which adds supply-chain and execution surface beyond the previous PATH-only exec.

Overview
cmux cr and passthrough cmux coderouter verbs no longer fail with a generic “CLI not found” when CodeRouter is absent. Passthrough logic moves into CMUXCLI+CoderouterPassthrough.swift, which resolves coderouter/cr on PATH, then ~/.coderouter/bin (or $CODEROUTER_INSTALL/bin), strips CMUX_* / CMUXD_*, and execves the real CLI unchanged.

When nothing resolves, interactive terminals get a one-time offer to run the documented installer: cmux downloads install.sh with curl (HTTPS, TLS 1.2+), runs it via sh (not a blind curl | sh), re-resolves, then execs. Non-interactive, declined, or failed installs print the exact curl -fsSL https://cmux.com/coderouter/install.sh | sh line on stderr and exit 127. Cmux-owned coderouter status|machines|claude over the app socket are unchanged.

Help text, docs/cli-contract.md, and localization (new cli.coderouter.bootstrap.* strings) document the new behavior; CLICoderouterBootstrapTests cover offer/decline/install paths via a DEBUG-only installer override.

Reviewed by Cursor Bugbot for commit d6d07db. Bugbot is set up for automated code reviews on this repo. Configure here.

austinywang and others added 2 commits September 8, 2026 03:42
Regression tests for #12139,
committed before the fix so CI shows them failing:

- non-interactive `cmux cr --version` with no coderouter/cr on PATH must
  print the exact install command and exit 127 without touching HOME
- a CodeRouter installed by the official installer (`~/.coderouter/bin`
  or `$CODEROUTER_INSTALL/bin`) must be exec'd even when PATH does not
  list it yet
- at a terminal, cmux must offer the documented installer once, run it
  after `y`, and exec the fresh install with the original arguments
- declining must install nothing, print the install command, exit 127

The interactive cases run the CLI on a pseudo-terminal and drive the
Debug-only CMUX_CODEROUTER_INSTALLER_SCRIPT seam with a local stand-in
for install.sh, so no test reaches the network. Every test isolates
HOME and PATH so a developer's own CodeRouter never takes part.

The superseded `missingExecutableIsActionable` test, which asserted the
old generic "Required CLI not found" message and that CodeRouter was
never named, moves into the new suite with the new contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWb19oD1CAyTEPLjiJpe9
`cmux cr` (and every `cmux coderouter` verb cmux does not own) exec'd
whatever `coderouter`/`cr` PATH offered and exited 127 otherwise, so on
a machine where CodeRouter was never installed the advertised alias was
dead on arrival.

The passthrough now lives in one place, CLI/CMUXCLI+CoderouterPassthrough.swift,
which owns resolve, bootstrap, and exec:

- resolution is PATH `coderouter`, PATH `cr`, then the official
  installer's own target (`$CODEROUTER_INSTALL/bin`, default
  `~/.coderouter/bin`), so an install whose shell-profile PATH line has
  not reached this process still runs; no hit makes a network call
- when nothing resolves and stdin and stderr are terminals, cmux shows
  the documented `curl -fsSL https://cmux.com/coderouter/install.sh | sh`,
  says what it does, asks once, and after `y` fetches the script with
  `/usr/bin/curl --proto =https --tlsv1.2` into a private temp dir and
  runs it with `/bin/sh` as a child (a failed or truncated download
  never reaches the shell), then re-resolves and execs the new install
  with the original arguments
- non-interactive, declined, download failure, and installer failure
  all print the exact install command on stderr and exit 127
- the child environment for the installer, curl, and CodeRouter itself
  is the same CMUX_*/CMUXD_*-stripped one as before

cmux.swift loses the moved code (net -86 lines); two of its private
helpers become internal for the new file. A Debug-only
CMUX_CODEROUTER_INSTALLER_SCRIPT seam lets the tests run the bootstrap
against a local stand-in for install.sh.

Localization: new cli.coderouter.bootstrap.* keys (en, ja); the unused
generic cli.coderouter.error.notFound is retired; the alias help line
changes in en and ja and its other locales are marked needs_review.
docs/cli-contract.md documents the contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWb19oD1CAyTEPLjiJpe9
@vercel

vercel Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
cmux166 Ready Ready Preview Sep 8, 2026 1:55pm UTC
cmux41 Ready Ready Preview Sep 8, 2026 1:55pm UTC

@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 32 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e9febbe6-16b0-42bf-bda8-b2e22a078600

📥 Commits

Reviewing files that changed from the base of the PR and between 5939eaf and d6d07db.

📒 Files selected for processing (9)
  • CLI/CMUXCLI+Coderouter.swift
  • CLI/CMUXCLI+CoderouterPassthrough.swift
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CLIAuthAliasTests.swift
  • cmuxTests/CLICoderouterBootstrapTests.swift
  • cmuxTests/CLICoderouterCommandTests.swift
  • docs/cli-contract.md

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.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@austinywang
austinywang marked this pull request as ready for review September 8, 2026 11:19
@austinywang
austinywang merged commit 1e3bd70 into main Sep 8, 2026
36 of 46 checks passed
austinywang added a commit that referenced this pull request Sep 8, 2026
Brings in #12131 (cmux notify inside a machine), #12154, #12144 (cmux cr
bootstrap), #12112 (cloud notifications with per-client acks), #10623,
#11358, #12118.

Conflicts resolved:
- cmux.xcodeproj/project.pbxproj: cmuxTests group children — kept both
  sides (CloudFileDeliveryTests from this branch, CloudNotificationSyncTests
  from main); normalized, test-wiring lint ok (813 test files).
- web/scripts/check-devbox-image-reachable.ts: took main's portable
  entry-point guard over this branch's typed import.meta.main fix.

Checks on the merged tree: 18 web suites 372 pass / 0 fail, tsc clean, shim
image copy byte-identical, sh -n clean, swiftc -parse on every file both
sides touched, VMClientError switch still exhaustive, no duplicate
definitions introduced.

Claude-Session: https://claude.ai/code/session_01QBDetMeke87gUWzvok9LWr
aerickson pushed a commit to aerickson/cmux that referenced this pull request Sep 13, 2026
…i#12144)

* test: cover cmux cr on a machine without CodeRouter (manaflow-ai#12139)

Regression tests for manaflow-ai#12139,
committed before the fix so CI shows them failing:

- non-interactive `cmux cr --version` with no coderouter/cr on PATH must
  print the exact install command and exit 127 without touching HOME
- a CodeRouter installed by the official installer (`~/.coderouter/bin`
  or `$CODEROUTER_INSTALL/bin`) must be exec'd even when PATH does not
  list it yet
- at a terminal, cmux must offer the documented installer once, run it
  after `y`, and exec the fresh install with the original arguments
- declining must install nothing, print the install command, exit 127

The interactive cases run the CLI on a pseudo-terminal and drive the
Debug-only CMUX_CODEROUTER_INSTALLER_SCRIPT seam with a local stand-in
for install.sh, so no test reaches the network. Every test isolates
HOME and PATH so a developer's own CodeRouter never takes part.

The superseded `missingExecutableIsActionable` test, which asserted the
old generic "Required CLI not found" message and that CodeRouter was
never named, moves into the new suite with the new contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWb19oD1CAyTEPLjiJpe9

* cli: bootstrap the CodeRouter CLI when cmux cr finds none (manaflow-ai#12139)

`cmux cr` (and every `cmux coderouter` verb cmux does not own) exec'd
whatever `coderouter`/`cr` PATH offered and exited 127 otherwise, so on
a machine where CodeRouter was never installed the advertised alias was
dead on arrival.

The passthrough now lives in one place, CLI/CMUXCLI+CoderouterPassthrough.swift,
which owns resolve, bootstrap, and exec:

- resolution is PATH `coderouter`, PATH `cr`, then the official
  installer's own target (`$CODEROUTER_INSTALL/bin`, default
  `~/.coderouter/bin`), so an install whose shell-profile PATH line has
  not reached this process still runs; no hit makes a network call
- when nothing resolves and stdin and stderr are terminals, cmux shows
  the documented `curl -fsSL https://cmux.com/coderouter/install.sh | sh`,
  says what it does, asks once, and after `y` fetches the script with
  `/usr/bin/curl --proto =https --tlsv1.2` into a private temp dir and
  runs it with `/bin/sh` as a child (a failed or truncated download
  never reaches the shell), then re-resolves and execs the new install
  with the original arguments
- non-interactive, declined, download failure, and installer failure
  all print the exact install command on stderr and exit 127
- the child environment for the installer, curl, and CodeRouter itself
  is the same CMUX_*/CMUXD_*-stripped one as before

cmux.swift loses the moved code (net -86 lines); two of its private
helpers become internal for the new file. A Debug-only
CMUX_CODEROUTER_INSTALLER_SCRIPT seam lets the tests run the bootstrap
against a local stand-in for install.sh.

Localization: new cli.coderouter.bootstrap.* keys (en, ja); the unused
generic cli.coderouter.error.notFound is retired; the alias help line
changes in en and ja and its other locales are marked needs_review.
docs/cli-contract.md documents the contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWb19oD1CAyTEPLjiJpe9

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – cmux41 — d6d07dbe Deployed Sep 8, 2026 by vercel[bot]
Preview – cmux166 — d6d07dbe Deployed Sep 8, 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.

cmux cr exits 127 on machines without CodeRouter installed: bootstrap the CodeRouter CLI instead of failing

1 participant