Skip to content

cmux ssh: forward invoking shell's PATH and auth socket to SSH sessions - #5405

Open
nickethier wants to merge 6 commits into
manaflow-ai:mainfrom
nickethier:fix/ssh-inherit-env
Open

nickethier wants to merge 6 commits into
manaflow-ai:mainfrom
nickethier:fix/ssh-inherit-env

Conversation

@nickethier

@nickethier nickethier commented Jun 5, 2026 •

Copy link
Copy Markdown

Summary

  • What changed? cmux ssh now forwards PATH, SHELL, and SSH_AUTH_SOCK from the invoking shell by default, so ProxyCommand helpers and other tools on your shell PATH are reachable from the SSH session. A new --inherit-env flag forwards the full caller environment (stale cmux socket and workspace variables are scrubbed automatically). Terminal startup also now honors a forwarded PATH before prepending the bundled cmux bin directory.

  • Why? When cmux ssh opens a managed terminal surface, the SSH process inherits the app's launch environment rather than the shell environment that invoked the CLI. PATH may differ significantly between the two, so ProxyCommand helpers configured in ssh_config can be unreachable even though they resolve correctly in the user's shell.

Testing

  • Added socket tests in WorkspaceSSHTests.swift covering the default forwarding allowlist (PATH, SHELL, SSH_AUTH_SOCK) and --inherit-env scrubbing behavior (stale cmux variables are removed, other env vars pass through). Tests have no dependency on fish or any non-standard shell.
  • Manually verified that cmux ssh with a ProxyCommand that depends on a shell-installed tool works after this change and fails before it.

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

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


Summary by cubic

cmux ssh now forwards PATH, SHELL, and SSH_AUTH_SOCK from your shell so ProxyCommand helpers and tools on your PATH work inside SSH sessions. Added --inherit-env to pass the full caller environment (stale cmux context and relay creds are scrubbed); terminal startup now prefers a forwarded PATH.

  • New Features

    • Default forwarding of PATH, SHELL, SSH_AUTH_SOCK; --inherit-env forwards all env vars while removing stale CMUX_* context.
    • Terminal startup prefers the forwarded PATH; tests cover the allowlist and scrubbing (fish dependency removed); docs/help updated across locales.
  • Bug Fixes

    • --inherit-env also scrubs CMUX_RELAY_ID, CMUX_RELAY_TOKEN, and CMUX_BUNDLED_CLI_PATH; tests now assert these and SSH_AUTH_SOCK forwarding.

Written for commit 0a76ba8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added --inherit-env flag to SSH to optionally forward the caller’s full environment while removing stale internal context.
    • SSH now forwards PATH, SHELL, and SSH_AUTH_SOCK from the invoking shell by default.
  • Documentation

    • Updated CLI help, docs, and localized strings across supported languages to document the new environment-forwarding behavior.
  • Tests

    • Added SSH integration tests covering default vs. --inherit-env environment forwarding and renamed the SSH test suite.

Nick Ethier added 3 commits June 4, 2026 18:03
When cmux ssh opens a terminal surface, it inherits the app's launch
environment rather than the invoking shell's environment. PATH may
differ significantly between the two, so ProxyCommand helpers and
other tools configured in ssh_config may not be reachable.

Forward PATH, SHELL, and SSH_AUTH_SOCK by default. Add --inherit-env
to pass the full caller environment, scrubbing stale cmux socket and
workspace variables. Teach terminal startup to honor a forwarded PATH
before prepending the bundled cmux bin directory.

Cover the default forwarding allowlist and --inherit-env scrubbing in
the cmux ssh socket tests.
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@nickethier

Copy link
Copy Markdown
Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 5, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@nickethier I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

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: 19d31f46-7dd6-4554-ad4d-17a345f73fe7

📥 Commits

Reviewing files that changed from the base of the PR and between 2582436 and 0a76ba8.

📒 Files selected for processing (1)
  • cmuxTests/WorkspaceSSHTests.swift

📝 Walkthrough

Walkthrough

This PR adds an --inherit-env CLI flag to cmux ssh that forwards the invoking shell's environment to remote SSH sessions. By default, only a safe allowlist (PATH, SHELL, SSH_AUTH_SOCK) is forwarded; with --inherit-env, the full environment is forwarded after scrubbing stale cmux and workspace variables. Tests, documentation, and 20 language localizations are updated.

Changes

SSH environment forwarding feature

Layer / File(s) Summary
SSH CLI environment forwarding implementation
CLI/cmux.swift
Added inheritEnvironment property to SSHCommandOptions, parse --inherit-env flag, define safe/scrubbed environment key sets, build startup environment from safe allowlist or scrubbed inherited environment, set SSH_AUTH_SOCK from resolved agent socket, and update workspace initialization and CLI help text.
Environment forwarding test coverage and bootstrap refactor
cmuxTests/WorkspaceSSHTests.swift, cmux.xcodeproj/project.pbxproj
Renamed test class and updated bootstrap to use /bin/sh instead of fish; added tests verifying safe environment forwarding by default and --inherit-env caller environment preservation with cmux context scrubbing; introduced captureSSHWorkspaceCreateParams helper to run CLI against mock socket and extract workspace.create parameters; updated Xcode project test references.
Documentation and localization updates
CHANGELOG.md, docs/cli-contract.md, Resources/Localizable.xcstrings, web/app/[locale]/docs/ssh/page.tsx, web/messages/* (20 files)
Updated changelog with feature entry; rewrote CLI contract description for environment forwarding defaults and --inherit-env semantics; added flagInheritEnv translation key across multiple web message files; added --inherit-env row to web SSH docs page; updated localized CLI usage/help strings.
Environment override integration
Sources/GhosttyTerminalView.swift
Updated PATH resolution to read from initialEnvironmentOverrides dictionary first, allowing environment forwarding customization to affect app PATH resolution.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#5301: Both PRs modify the cmux ssh CLI’s workspace/session environment injection—especially around SSH_AUTH_SOCK (default forwarding/inheritance with scrubbing in main PR vs opt-in agent forwarding deriving and propagating agentSocketPath in retrieved PR)—so they touch overlapping environment-forwarding code paths.

Suggested reviewers

  • Ari4ka

Poem

🐰 A rabbit nibbles code tonight,
Flags and envs aligned just right,
PATH and SHELL hop into view,
--inherit-env lets more pass through,
Old CMUX traces swept from sight.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error The function free in Sources/GhosttyTerminalView.swift line 1562 is declared as nonisolated static async but lacks required @concurrent annotation per swift-concurrent-annotation.md. Add @concurrent annotation to the free function: @concurrent private nonisolated static func free(...) to comply with Swift 6 NonisolatedNonsendingByDefault behavior.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: forwarding PATH and SSH_AUTH_SOCK from the invoking shell to SSH sessions, which is the primary objective of this changeset.
Description check ✅ Passed The description covers the required sections (Summary, Testing, Checklist) with substantial detail. Summary explains what changed and why, Testing details the socket tests added and manual verification, and most checklist items are checked. The description is well-structured and comprehensive.
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 changes add inheritEnvironment to SSHCommandOptions (a pure Sendable struct) and modify @MainActor createSurface with local variable reassignment; no isolation violations found.
Cmux Swift Blocking Runtime ✅ Passed Production code adds environment variable forwarding without blocking synchronization—only pure synchronous filtering and data structure operations.
Cmux No Hacky Sleeps ✅ Passed PR modifies only documentation, localization strings, and Swift code. No production non-Swift runtime code was added or modified with sleep/timeout patterns.
Cmux Algorithmic Complexity ✅ Passed Code operates on fixed-size collections (3 safe keys, 13 scrubbed keys, bounded environment). Functions called once per SSH session, not in loops. No nested scans or hot-path violations found.
Cmux Swift Concurrency ✅ Passed All new SSH environment code uses synchronous patterns. Test-only DispatchQueue usage for process/socket coordination is acceptable per concurrency rules.
Cmux Swift File And Package Boundaries ✅ Passed PR adds 63 lines to CLI/cmux.swift (below 250-line threshold), no new files created, no mixed-responsibility violations, and no package boundary issues triggered.
Cmux Swift Logging ✅ Passed SSH environment changes use proper cliDebugLog system with no secrets logged; no print/NSLog/debugPrint in app code; tests verify scrubbing of sensitive env vars.
Cmux User-Facing Error Privacy ✅ Passed User-facing help/docs mention "cmux variables" generically, not exposing sensitive token names. Documentation exceptions are allowed per policy.
Cmux Full Internationalization ✅ Passed CLI help text uses String(localized:) with complete Localizable.xcstrings translations; web flagInheritEnv key in all routing locales; CHANGELOG addition allowed as pre-existing untranslated file.
Cmux Swiftui State Layout ✅ Passed No new SwiftUI state patterns added. GhosttyTerminalView PATH priority change is in lifecycle method createSurface, not render function.
Cmux Architecture Rethink ✅ Passed PR adds environment forwarding for SSH via stateless helper functions with clear owners and invariants. No timing repairs, duplicate state owners, or split lifecycle ownership introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change standalone windows; only updates PATH environment resolution in pre-existing internal headless startup window.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr view 5405 --repo manaflow-ai/cmux --json title,body,files,additions,deletions 2>/dev/null | jq '{title, additions, deletions, files: [.files[].path]}'

Length of output: 1198


🏁 Script executed:

#!/bin/bash
gh pr diff 5405 --repo manaflow-ai/cmux 2>/dev/null | head -800

Length of output: 47505


🏁 Script executed:

#!/bin/bash
# Find normalizedEnvValue definition
rg -n 'normalizedEnvValue' CLI/cmux.swift | head -20

Length of output: 1230


🏁 Script executed:

#!/bin/bash
# Check for CMUX_ / CMUXD_ env var names set anywhere that might be missing from the scrub list
rg -n 'setenv\s*\(\s*"CMUX' CLI/cmux.swift | grep -v '//\s*' | head -30
rg -n '"CMUX_[A-Z_]+"' CLI/cmux.swift | grep -v '//' | grep -v 'safeSSHStartupEnvironment\|sshInheritedEnvironment\|startupEnvironment\[' | head -40

Length of output: 6443


🏁 Script executed:

#!/bin/bash
# Check what initial_env consumer does in GhosttyTerminalView - confirm initialEnvironmentOverrides is set from initial_env
rg -n 'initialEnvironmentOverrides' Sources/GhosttyTerminalView.swift | head -20

Length of output: 745



@nickethier: The overall approach is clean and well-structured — removing the fish dependency from the test, the initialEnvironmentOverrides["PATH"] priority fix in GhosttyTerminalView.swift, and the agentSocketPath correctly overriding an inherited SSH_AUTH_SOCK are all correct. A couple of things to flag:


🔴 Incomplete scrub list for --inherit-env — relay credentials may leak

sshInheritedEnvironmentScrubbedKeys covers the obvious socket/workspace variables, but several other cmux-internal variables that are setenv'd in the process are not scrubbed. The most sensitive:

CMUX_RELAY_ID
CMUX_RELAY_TOKEN          ← relay auth token — security-sensitive
CMUX_AGENT_LAUNCH_KIND
CMUX_AGENT_LAUNCH_EXECUTABLE
CMUX_AGENT_LAUNCH_ARGV_B64
CMUX_AGENT_LAUNCH_CWD
CMUX_CODEX_TEAMS_THREAD_ID
CMUX_CODEX_TEAMS_PARENT_THREAD_ID
CMUX_CODEX_TEAMS_DEPTH
CMUX_CODEX_TEAMS_APP_SERVER_URL
CMUX_CODEX_TEAMS_MAX_AUTO_DEPTH
CMUX_ORIGINAL_NODE_OPTIONS_PRESENT
CMUX_ORIGINAL_NODE_OPTIONS
CMUX_SUPPRESS_SUBAGENT_NOTIFICATIONS
CMUX_AGENT_MANAGED_SUBAGENT
CMUX_TAG
CMUX_BUNDLE_ID

A user running cmux ssh --inherit-env from inside an existing cmux session will silently forward all of those. The relay token in particular (CMUX_RELAY_TOKEN) is auth material that shouldn't cross the SSH boundary.

Consider switching to a prefix-based block instead of a denylist, which is automatically future-proof:

private static let sshInheritedEnvironmentScrubbedPrefixes: [String] = [
    "CMUX_",
    "CMUXD_",
]

private func scrubbedSSHInheritedEnvironment(_ environment: [String: String]) -> [String: String] {
    var result = environment.compactMapValues { Self.normalizedEnvValue($0) }
    result = result.filter { key in
        !Self.sshInheritedEnvironmentScrubbedPrefixes.contains(where: { key.hasPrefix($0) })
    }
    return result
}

If there are specific CMUX_ variables that should pass through (e.g., CMUX_BUNDLE_ID for identification), they can be added back explicitly after the prefix filter.


🟡 Safe-forward test doesn't verify SSH_AUTH_SOCK

testSSHForwardsSafeCallerEnvironmentByDefault asserts PATH and SHELL are present but never passes SSH_AUTH_SOCK in the environment overrides and asserts it is forwarded. Given that SSH_AUTH_SOCK is one of the three keys in safeSSHStartupEnvironmentKeys, worth adding:

environmentOverrides: [
    "PATH": "/opt/work/bin:/usr/bin:/bin",
    "SHELL": "/opt/homebrew/bin/fish",
    "SSH_AUTH_SOCK": "/tmp/ssh-agent.sock",
    "WORK_TOKEN": "secret-token",
]
// …
XCTAssertEqual(initialEnv["SSH_AUTH_SOCK"], "/tmp/ssh-agent.sock")

(Note: when --forward-agent is not passed and agentSocketPath is nil, the safe path should forward the caller's SSH_AUTH_SOCK directly.)


✅ Everything else looks good

  • The initialEnvironmentOverrides["PATH"] priority fix in GhosttyTerminalView.swift is the right fix — it ensures the forwarded PATH is used as the base when prepending the bundled bin/ dir.
  • agentSocketPath correctly takes final precedence over any inherited SSH_AUTH_SOCK.
  • Flag parsing, help text, docs, and i18n strings are all consistent.
  • The normalizedEnvValue null/empty guard is appropriately applied in both safeSSHStartupEnvironment and scrubbedSSHInheritedEnvironment.

🧠 Learnings used
Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:4036-4038
Timestamp: 2026-04-01T09:50:41.471Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift (buildInteractiveRemoteShellScript), remote shells intentionally export CMUX_SOCKET_PATH and CMUX_SOCKET as "127.0.0.1:<relayPort>" and prepend PATH with "$HOME/.cmux/bin"; CMUX_BUNDLED_CLI_PATH points to that wrapper. WorkspaceRemoteSessionController.remoteCLIWrapperScript() detects host:port, reads "~/.cmux/relay/<port>.daemon_path", and delegates to cmuxd-remote. Do not flag the non-UNIX value of CMUX_SOCKET_PATH/CMUX_SOCKET as a bug; it is the relay contract for remote sessions.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2564
File: Sources/Workspace.swift:1072-1075
Timestamp: 2026-04-04T02:21:20.453Z
Learning: Repo: manaflow-ai/cmux — Foreground-auth deferral: In CLI/cmux.swift, PermitLocalCommand and LocalCommand are injected only into the foreground startup SSH command (e.g., via deferredRemoteReconnectLocalCommand(...)) and are not added to the ssh_options payload sent in workspace.remote.configure. Consequently, WorkspaceRemoteSSHBatchCommandBuilder.batchArguments(...) and WorkspaceRemoteSessionController.backgroundSSHOptions(_:) do not need to filter LocalCommand/PermitLocalCommand for batch operations.

Learnt from: outoftime
Repo: manaflow-ai/cmux PR: 1528
File: Sources/GhosttyTerminalView.swift:3220-3228
Timestamp: 2026-03-17T18:25:33.286Z
Learning: Repo: manaflow-ai/cmux — In Sources/GhosttyTerminalView.swift within TerminalSurface.createSurface(for:), when constructing XDG_DATA_DIRS for Fish vendor_conf.d auto-sourcing, treat empty or whitespace-only values from initialEnvironmentOverrides, env, getenv, and ProcessInfo as unset before prefixing the integrationDir. This avoids producing a trailing colon. Keep XDG_DATA_DIRS in protectedStartupEnvironmentKeys so initialEnvironmentOverrides cannot overwrite the prefixed value.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2564
File: CLI/cmux.swift:4032-4035
Timestamp: 2026-04-04T02:19:33.834Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift (cmux ssh foreground-auth deferral), deferredRemoteReconnectLocalCommand(in:localCLIPath:foregroundAuthToken:) prefers resolvedExecutableURL()?.path for the local cmux binary, then falls back to CMUX_BUNDLED_CLI_PATH and finally PATH; it reads the local socket from ${CMUX_SOCKET_PATH:-${CMUX_SOCKET:-}}. This ensures the deferred LocalCommand runs locally (pre-auth) when auto_connect is false.

Learnt from: pstanton237
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-06T12:03:16.921Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift `runClaudeTeams`, `setenv("CMUX_CLAUDE_PID", ...)` is set unconditionally before the passthrough-subcommand check, mirroring `Resources/bin/claude:188` which exports `CMUX_CLAUDE_PID=$$` before the subcommand `case` statement. This is intentional design: the env var is dead for `mcp`/`config`/`api-key`/`rc`/`remote-control` paths but harmless, and matching the bash wrapper unconditional export exactly is a deliberate design principle for the claude-teams hook injection feature.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2564
File: CLI/cmux.swift:0-0
Timestamp: 2026-04-04T02:33:06.558Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift, baseSSHArguments(_:, localCommand:) now percent-escapes LocalCommand by replacing "%" with "%%" to prevent OpenSSH percent-token expansion. A CLI regression test asserts that the emitted -o LocalCommand retains doubled percent signs.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2398
File: CLI/cmux.swift:0-0
Timestamp: 2026-04-01T09:50:23.728Z
Learning: Repo: manaflow-ai/cmux — In CLI/cmux.swift within CMUXCLI.buildInteractiveRemoteShellScript(...), never export CMUX_TAB_ID from the workspace UUID. CMUX_TAB_ID must be surface-scoped: only set it when a surface ID is available (map CMUX_TAB_ID to CMUX_SURFACE_ID). Rationale: tab-action/rename-tab resolve CMUX_TAB_ID before CMUX_SURFACE_ID; workspace-scoped values misroute or fail.

Learnt from: lawrencecchen
Repo: manaflow-ai/cmux PR: 2564
File: Sources/Workspace.swift:5253-5264
Timestamp: 2026-04-04T02:19:25.076Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift, WorkspaceRemoteSessionController.parsePathHelperPaths(_:) must anchor on the actual PATH assignment (split helper output on newline/semicolon, find the fragment starting with PATH="…", then extract the quoted value) to avoid matching MANPATH or other vars that contain “PATH=”.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-28T22:24:18.018Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift (PR `#3237`, commit 9dcb9c2), the `newTerminalSurface(...)` method gained an `allowInitialInputWithRemoteStartupCommand: Bool` parameter (default `false`). The session-restore call site in `createPanel(from:inPane:)` passes `true` only when `!panelWasRemoteBacked && combinedInitialInput != nil`, making the per-panel snapshot flag the sole authority for restore-time `initialInput` injection. All 12 other call sites keep `false`, preserving the existing remote-drop defense.

Learnt from: mrosnerr
Repo: manaflow-ai/cmux PR: 0
File: :0-0
Timestamp: 2026-04-05T21:26:10.710Z
Learning: Repo: manaflow-ai/cmux — In Sources/Workspace.swift, applySessionPanelMetadata() must gate listeningPorts restoration on the per-panel snapshot.terminal?.isRemoteBacked flag (not workspace-wide remoteTerminalStartupCommand()), so that local panels are always eligible for port restore regardless of current SSH state. Fixed in commit 4d0fd871 (PR `#2545`).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4564085c34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CLI/cmux.swift
Comment on lines +7717 to 7720
let startupEnvironment = sshStartupEnvironment(for: sshOptions)
if !startupEnvironment.isEmpty {
workspaceCreateParams["initial_env"] = startupEnvironment
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist forwarded env for reusable SSH terminals

When the user creates another terminal/split in this SSH workspace, the app runs the saved terminal_startup_command from workspace.remote.configure, not the workspace.create initial_env. The new PATH/SHELL/--inherit-env values are only attached to the first terminal here; later remote terminals go through Workspace.terminalStartupEnvironment, which currently pulls only remoteConfiguration.sshTerminalStartupEnvironment (SSH_AUTH_SOCK only), so ProxyCommand helpers on the caller's PATH still disappear for subsequent SSH sessions.

Useful? React with 👍 / 👎.

Comment thread CLI/cmux.swift
Comment on lines +8003 to +8007
private static let safeSSHStartupEnvironmentKeys: [String] = [
"PATH",
"SHELL",
"SSH_AUTH_SOCK",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate SSH_AUTH_SOCK before forwarding it

With a stale SSH_AUTH_SOCK in the invoking shell, this default safe-env path forwards the raw value even when resolvedSSHAgentForwarding rejects it because the socket does not exist. In that scenario the later agentSocketPath override is nil, so the newly created terminal still receives the dead socket and OpenSSH can fail agent/config cases (e.g. ForwardAgent yes/ask) that previously fell back to the app environment instead of injecting a stale caller socket.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR teaches cmux ssh to forward PATH, SHELL, and SSH_AUTH_SOCK from the invoking shell by default, fixing broken ProxyCommand helpers, and adds --inherit-env for users who need their full caller environment in the SSH session with stale CMUX context automatically scrubbed.

  • Environment forwarding: Default safe-forwarding allowlist (PATH, SHELL, SSH_AUTH_SOCK) replaces the previous agent-socket-only path; --inherit-env takes the whole caller environment and removes 13 CMUX-internal keys including relay credentials (CMUX_RELAY_ID, CMUX_RELAY_TOKEN) and all socket/workspace identifiers.
  • Terminal PATH precedence: GhosttyTerminalView now checks initialEnvironmentOverrides["PATH"] before the app launch environment, so a forwarded PATH is actually honoured when prepending the bundled bin directory.
  • Localization: All 20 locales in Localizable.xcstrings and all web/messages/*.json files are updated with the --inherit-env description; tests are renamed and the fish-shell dependency is removed in favour of /bin/sh.

Confidence Score: 5/5

Safe to merge — all CMUX credentials and workspace context are scrubbed from the inherited environment, the safe default allowlist is strictly limited to three keys, and all 20 locales are updated.

The relay credentials (CMUX_RELAY_ID, CMUX_RELAY_TOKEN) that were flagged as missing in a prior review are now present in the scrub list. All locale entries in both xcstrings and web/messages are updated. The PATH precedence fix in GhosttyTerminalView is minimal and correct. New socket tests exercise both code paths end-to-end without any external shell dependency.

No files require special attention.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds --inherit-env flag, sshStartupEnvironment helper, and three-key safe allowlist (PATH/SHELL/SSH_AUTH_SOCK); relay credentials and all CMUX context vars are properly scrubbed in the inherit-env path.
Resources/Localizable.xcstrings All 20 locale entries for the SSH help string now include the --inherit-env flag description; the previous gap in non-en/ja locales is fully resolved.
Sources/GhosttyTerminalView.swift PATH resolution now prefers initialEnvironmentOverrides[PATH] before falling back to the app launch environment, so a forwarded PATH is honoured when prepending the bundled bin directory.
cmuxTests/WorkspaceSSHTests.swift Renamed from WorkspaceSSHFishShellTests; fish dependency removed; two new socket tests validate default allowlist forwarding and full --inherit-env scrubbing including relay credentials.
web/app/[locale]/docs/ssh/page.tsx Adds --inherit-env row to the flags table, consuming flagInheritEnv from the locale message files.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[cmux ssh invoked] --> B{--inherit-env?}
    B -- No --> C[safeSSHStartupEnvironment]
    B -- Yes --> D[scrubbedSSHInheritedEnvironment]
    C --> E[Pick PATH, SHELL, SSH_AUTH_SOCK\nfrom caller env]
    D --> F[Copy full caller env\ncompactMapValues via normalizedEnvValue]
    F --> G[Remove 13 CMUX keys:\nSOCKET, WORKSPACE_ID, SURFACE_ID,\nRELAY_ID, RELAY_TOKEN, etc.]
    E --> H{agentSocketPath set?}
    G --> H
    H -- Yes --> I[Override SSH_AUTH_SOCK\nwith CLI-resolved agent socket]
    H -- No --> J{startupEnvironment empty?}
    I --> J
    J -- No --> K[Set initial_env in workspace.create params]
    J -- Yes --> L[Omit initial_env]
    K --> M[GhosttyTerminalView: initialEnvironmentOverrides PATH\ntakes precedence when prepending bundled bin]
Loading

Reviews (6): Last reviewed commit: "tests: cover CMUX_BUNDLED_CLI_PATH scrub..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes cmux ssh forward PATH, SHELL, and SSH_AUTH_SOCK from the invoking shell by default so ProxyCommand helpers on the user's shell PATH are reachable from the SSH session, and adds --inherit-env to forward the caller's full environment with stale cmux socket/workspace variables scrubbed. The terminal startup path is also updated to honour a forwarded PATH before prepending the bundled cmux bin directory.

  • CLI/cmux.swift — adds inheritEnvironment to SSHCommandOptions, parses --inherit-env, and introduces sshStartupEnvironment(for:) with a safe allowlist path and a scrubbed full-env path.
  • Resources/Localizable.xcstrings — updated the SSH help string for en and ja only; 18 other translated locales are missing the new flag description.
  • Web and tests — all 20 web/messages/ locale files received flagInheritEnv; two new socket tests cover the default allowlist and --inherit-env scrubbing.

Confidence Score: 4/5

Safe to merge for most users; the only affected path is CLI help text in 18 non-English/non-Japanese locales showing outdated flag listings.

The environment forwarding logic, PATH lookup change, and scrub list are all correct. The one concrete gap is that 18 of 20 translated locales in Localizable.xcstrings did not receive the --inherit-env entry in the SSH help string, so users whose device language maps to Arabic, Bosnian, Danish, German, Spanish, French, Italian, Khmer, Korean, Norwegian, Polish, Portuguese (Brazil), Russian, Thai, Turkish, Ukrainian, Simplified Chinese, or Traditional Chinese will see CLI help output that never mentions the new flag.

Resources/Localizable.xcstrings — the SSH help string block at lines 11171–11290 needs the --inherit-env line added to all 18 remaining locale entries.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds --inherit-env flag, sshStartupEnvironment helpers, and scrub-list constants; logic is sound with no actor-isolation or blocking-runtime issues.
Sources/GhosttyTerminalView.swift Adds initialEnvironmentOverrides[PATH] as the first fallback in the PATH-prepend chain, correctly prioritising a forwarded PATH before the app-launch env.
Resources/Localizable.xcstrings SSH help string updated for en and ja only; 18 other translated locales still carry the old string without --inherit-env.
cmuxTests/WorkspaceSSHTests.swift Renamed from WorkspaceSSHFishShellTests; fish dependency removed; two new socket tests cover the default allowlist and --inherit-env scrubbing behaviour.
web/app/[locale]/docs/ssh/page.tsx Adds flagInheritEnv row to the SSH flags table; all 20 web locales have the matching translation key added.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[cmux ssh invoked] --> B{--inherit-env?}
    B -- No --> C[safeSSHStartupEnvironment]
    B -- Yes --> D[scrubbedSSHInheritedEnvironment]
    C --> E[Allow PATH, SHELL, SSH_AUTH_SOCK only]
    D --> F[Copy full caller environment]
    F --> G[Drop empty-valued variables]
    G --> H[Remove CMUX_SOCKET, CMUX_SOCKET_PATH, CMUX_SOCKET_PASSWORD, CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, CMUX_PANEL_ID, CMUX_TAB_ID, CMUXD_UNIX_PATH, CMUX_DEBUG_LOG]
    E --> I{agentSocketPath set?}
    H --> I
    I -- Yes --> J[Override SSH_AUTH_SOCK with agent path]
    I -- No --> K[startupEnvironment unchanged]
    J --> L{startupEnvironment empty?}
    K --> L
    L -- No --> M[Set initial_env in workspaceCreateParams]
    L -- Yes --> N[Omit initial_env]
    M --> O[SSH session started with forwarded env]
    N --> O
Loading

Reviews (2): Last reviewed commit: "tests: remove fish dependency from Works..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes cmux ssh forward PATH, SHELL, and SSH_AUTH_SOCK from the invoking shell by default, and adds --inherit-env to forward the full caller environment with stale cmux context variables scrubbed. The GhosttyTerminalView startup PATH logic is updated to honour forwarded overrides before falling back to the app's ambient environment.

  • Default forwarding (safeSSHStartupEnvironmentKeys): PATH, SHELL, SSH_AUTH_SOCK — correctly allowlisted and small; test coverage is solid.
  • --inherit-env scrub list (sshInheritedEnvironmentScrubbedKeys): blocks stale socket/workspace variables but omits CMUX_RELAY_TOKEN, CMUX_RELAY_ID, and CMUX_PANE_ID; relay credentials present in a relay-enabled terminal would be forwarded to the remote host.
  • i18n: all 28 web locale files are updated correctly, but Resources/Localizable.xcstrings updates only 2 of 20 existing locales (en, ja) for the SSH help text.

Confidence Score: 3/5

The core forwarding logic is well-structured and opt-in by default, but two issues need resolution before merging: relay credentials can leak to remote hosts via --inherit-env, and 18 app locales are left without the new flag description.

The default-path change (always forwarding PATH/SHELL/SSH_AUTH_SOCK) is low-risk and well-tested. The --inherit-env path has a real gap: CMUX_RELAY_TOKEN and CMUX_RELAY_ID are omitted from the scrub list, so a user in a relay-enabled workspace who passes --inherit-env sends those credentials to the remote host. Independently, the xcstrings file only updated 2 of 20 locales, leaving 18 language communities with a help string that doesn't document the new flag.

CLI/cmux.swift (sshInheritedEnvironmentScrubbedKeys) and Resources/Localizable.xcstrings (18 locales missing --inherit-env description)

Security Review

  • CMUX_RELAY_TOKEN and CMUX_RELAY_ID are absent from sshInheritedEnvironmentScrubbedKeys in CLI/cmux.swift. These are relay authentication credentials that can be present in a user's terminal environment inside a relay-enabled workspace. When --inherit-env is passed, they are forwarded to the remote SSH host, allowing that host to use the credentials against the relay API.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds --inherit-env flag, sshStartupEnvironment helper, safeSSHStartupEnvironmentKeys allowlist, and sshInheritedEnvironmentScrubbedKeys blocklist; scrub list is missing CMUX_RELAY_TOKEN, CMUX_RELAY_ID, and CMUX_PANE_ID
Resources/Localizable.xcstrings SSH help string updated for en and ja only; 18 other fully-translated locales are missing the new --inherit-env description
Sources/GhosttyTerminalView.swift Correctly extends the PATH-resolution chain to check initialEnvironmentOverrides[PATH] first, so a forwarded PATH is honoured before falling back to the app's ambient environment
cmuxTests/WorkspaceSSHTests.swift New socket-level tests cover safe allowlist forwarding and --inherit-env scrubbing of cmux context variables; DispatchSemaphore use is test scaffolding only
web/app/[locale]/docs/ssh/page.tsx Adds --inherit-env row to the flags table, correctly consuming the flagInheritEnv i18n key; all web locale files are updated

Sequence Diagram

sequenceDiagram
    participant Shell as User Shell
    participant CLI as cmux CLI
    participant Env as ProcessInfo.environment
    participant API as cmux API

    Shell->>CLI: cmux ssh [--inherit-env] host
    CLI->>Env: Read caller environment

    alt default (no --inherit-env)
        Env-->>CLI: safeSSHStartupEnvironmentKeys (PATH, SHELL, SSH_AUTH_SOCK)
        CLI->>CLI: Override SSH_AUTH_SOCK if --forward-agent resolved
    else --inherit-env
        Env-->>CLI: Full environment
        CLI->>CLI: scrubbedSSHInheritedEnvironment() Remove CMUX_SOCKET(_PATH), CMUX_WORKSPACE_ID, CMUX_SURFACE_ID, CMUX_PANEL_ID, CMUX_TAB_ID, CMUXD_UNIX_PATH, CMUX_DEBUG_LOG (CMUX_RELAY_TOKEN/ID not scrubbed)
        CLI->>CLI: Override SSH_AUTH_SOCK if --forward-agent resolved
    end

    CLI->>API: "workspace.create initial_env = startupEnvironment"
    API-->>CLI: workspace_id
Loading

Comments Outside Diff (1)

  1. Resources/Localizable.xcstrings, line 11174-11290 (link)

    P1 18 locales missing --inherit-env in SSH help text

    The --inherit-env line was added to the en and ja entries of the SSH command help string, but 18 other locales that already have full translations of this string were not updated: ar, bs, da, de, es, fr, it, km, ko, no, pl, pt-BR, ru, th, tr, uk, zh-CN, and zh-TW. Users in those locales will see an --inherit-env flag in the running CLI (since the flag is real) but no corresponding description in the --help output pulled from the string catalog, leaving the behavior silently undocumented in every non-English, non-Japanese locale.

    Rule Used: Flag production user-facing text that is not fully... (source)

    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!

Reviews (2): Last reviewed commit: "tests: remove fish dependency from Works..." | Re-trigger Greptile

Comment on lines 11195 to 11224
@@ -11219,7 +11219,7 @@
"ja": {
"stringUnit": {
"state": "translated",
"value": "Usage: cmux ssh <destination> [flags] [-- <remote-command-args>]\n\n新しいワークスペースを作成し、remote-SSH としてマークして、そのワークスペースで SSH セッションを開始します。\ncmux はローカル SSH プロキシエンドポイントも確立するため、ブラウザトラフィックはリモートホストから送信されます。\n\nFlags:\n --name <title> 任意のワークスペースタイトル\n --port <n> SSH ポート\n --identity <path> SSH identity ファイルパス\n -A, --forward-agent 呼び出し元の SSH エージェントを転送します。ssh_config の ForwardAgent yes も尊重します\n -a, --no-forward-agent このワークスペースでは SSH エージェント転送を無効にします\n --ssh-option <opt> 追加の SSH -o オプション(繰り返し可)\n --window <id|ref|index> 管理対象ワークスペースのターゲットウィンドウ\n --no-focus ワークスペースを作成しますが切り替えません\n\nExample:\n cmux ssh dev@my-host\n cmux ssh dev@my-host --name \"gpu-box\" --port 2222 --identity ~/.ssh/id_ed25519\n cmux ssh dev@my-host --forward-agent\n cmux ssh dev@my-host --ssh-option UserKnownHostsFile=/dev/null --ssh-option StrictHostKeyChecking=no"
"value": "Usage: cmux ssh <destination> [flags] [-- <remote-command-args>]\n\n新しいワークスペースを作成し、remote-SSH としてマークして、そのワークスペースで SSH セッションを開始します。\ncmux はローカル SSH プロキシエンドポイントも確立するため、ブラウザトラフィックはリモートホストから送信されます。\n\nFlags:\n --name <title> 任意のワークスペースタイトル\n --port <n> SSH ポート\n --identity <path> SSH identity ファイルパス\n -A, --forward-agent 呼び出し元の SSH エージェントを転送します。ssh_config の ForwardAgent yes も尊重します\n -a, --no-forward-agent このワークスペースでは SSH エージェント転送を無効にします\n --ssh-option <opt> 追加の SSH -o オプション(繰り返し可)\n --inherit-env 古い cmux コンテキストを除外して呼び出し元の環境を転送します\n --window <id|ref|index> 管理対象ワークスペースのターゲットウィンドウ\n --no-focus ワークスペースを作成しますが切り替えません\n\nExample:\n cmux ssh dev@my-host\n cmux ssh dev@my-host --name \"gpu-box\" --port 2222 --identity ~/.ssh/id_ed25519\n cmux ssh dev@my-host --forward-agent\n cmux ssh dev@my-host --ssh-option UserKnownHostsFile=/dev/null --ssh-option StrictHostKeyChecking=no"
}
},

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 Xcstrings SSH help text updated for en and ja only

The Localizable.xcstrings file contains translated SSH help strings for 20 locales. This PR updates en and ja to include --inherit-env, but the other 18 locales — ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant — still carry the old string without the new flag. Any user whose device language maps to one of these locales will see CLI help output that does not mention --inherit-env at all, making the flag effectively undiscoverable from cmux ssh --help. The web messages/*.json files were all updated correctly; the xcstrings gap is the only gap.

Rule Used: Flag production user-facing text that is not fully... (source)

Comment thread CLI/cmux.swift
Comment on lines +8042 to +8048
private func scrubbedSSHInheritedEnvironment(_ environment: [String: String]) -> [String: String] {
var result = environment.compactMapValues { Self.normalizedEnvValue($0) }
for key in Self.sshInheritedEnvironmentScrubbedKeys {
result.removeValue(forKey: key)
}
return result
}

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 --inherit-env silently drops empty-valued variables

scrubbedSSHInheritedEnvironment applies compactMapValues { Self.normalizedEnvValue($0) }, which strips any variable whose value is empty or whitespace-only. A user who relies on SOME_FLAG="" to disable a downstream tool will find the variable absent in the SSH session even though --inherit-env is documented as forwarding the full environment. This is an undocumented limitation; a comment or doc update noting that zero-length values are not forwarded would prevent confusion.

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!

Comment thread CLI/cmux.swift
Comment on lines +8009 to +8019
private static let sshInheritedEnvironmentScrubbedKeys: Set<String> = [
"CMUX_SOCKET",
"CMUX_SOCKET_PATH",
"CMUX_SOCKET_PASSWORD",
"CMUX_WORKSPACE_ID",
"CMUX_SURFACE_ID",
"CMUX_PANEL_ID",
"CMUX_TAB_ID",
"CMUXD_UNIX_PATH",
"CMUX_DEBUG_LOG",
]

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 security Relay credentials and pane ID missing from scrub list

CMUX_RELAY_TOKEN and CMUX_RELAY_ID are relay authentication credentials read from the process environment at runtime (used at line ~1926). If a user invokes cmux ssh --inherit-env from inside a relay-enabled workspace where these variables are set, they will be forwarded verbatim to the remote SSH host, allowing processes there to authenticate against the relay API with the user's credentials. CMUX_PANE_ID is also absent — it is a workspace-scoped surface identifier used alongside CMUX_SURFACE_ID (which is scrubbed) and should be treated the same way. CMUX_SOCKET_PASSWORD is correctly included, so the omission of the relay tokens appears to be an oversight rather than a deliberate choice.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 5, 2026

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

🤖 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 `@CHANGELOG.md`:
- Around line 7-8: The markdown heading "### Changed" needs a blank line
following it to satisfy MD022; edit the CHANGELOG.md entry containing the "###
Changed" heading and insert one empty line between that heading and the
subsequent bullet text (the line that begins "- `cmux ssh`...") so there is a
blank line after the "### Changed" heading.

In `@CLI/cmux.swift`:
- Around line 8009-8019: The environment scrub list
sshInheritedEnvironmentScrubbedKeys omits CMUX_BUNDLED_CLI_PATH which allows
stale cmux CLI paths to be inherited and used by
deferredRemoteReconnectLocalCommand's LocalCommand fallback when
resolvedExecutableURL() is unavailable; add "CMUX_BUNDLED_CLI_PATH" to the
sshInheritedEnvironmentScrubbedKeys Set so the variable is removed from
--inherit-env and cannot leak into managed SSH workspaces.

In `@cmuxTests/WorkspaceSSHTests.swift`:
- Around line 279-292: Add explicit assertions that SSH_AUTH_SOCK is forwarded
in both env-forwarding tests: in testSSHForwardsSafeCallerEnvironmentByDefault
(which obtains captured via captureSSHWorkspaceCreateParams and inspects
initial_env) assert that initialEnv["SSH_AUTH_SOCK"] is present and equals the
value passed in environmentOverrides; do the same in the sibling test that
exercises --inherit-env (the test around lines 295-321) to assert SSH_AUTH_SOCK
is preserved after scrubbing/inheritance. Locate the checks using the captured
dictionary (captured["initial_env"] as? [String: String]) and add the
SSH_AUTH_SOCK assertions alongside the existing PATH/SHELL and token/cmux-key
assertions.
🪄 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: dfa352fc-898f-4c38-bda8-f837ab999d5d

📥 Commits

Reviewing files that changed from the base of the PR and between 45603c6 and 4564085.

📒 Files selected for processing (28)
  • CHANGELOG.md
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/GhosttyTerminalView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/WorkspaceSSHTests.swift
  • docs/cli-contract.md
  • web/app/[locale]/docs/ssh/page.tsx
  • web/messages/ar.json
  • web/messages/bs.json
  • web/messages/da.json
  • web/messages/de.json
  • web/messages/en.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/messages/it.json
  • web/messages/ja.json
  • web/messages/km.json
  • web/messages/ko.json
  • web/messages/no.json
  • web/messages/pl.json
  • web/messages/pt-BR.json
  • web/messages/ru.json
  • web/messages/th.json
  • web/messages/tr.json
  • web/messages/uk.json
  • web/messages/zh-CN.json
  • web/messages/zh-TW.json

Comment thread CHANGELOG.md
Comment thread CLI/cmux.swift
Comment thread cmuxTests/WorkspaceSSHTests.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.

Caution

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

⚠️ Outside diff range comments (1)
cmuxTests/WorkspaceSSHTests.swift (1)

300-326: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a generic CMUX_* sentinel to lock in the scrub contract.

CMUX_RELAY_ID and CMUX_RELAY_TOKEN cover the currently known names, but this test still passes if a future CMUX_* secret leaks through a denylist regression. Add one unrelated CMUX_TEST_SENTINEL and assert it is removed so the suite protects the prefix-level privacy guarantee.

Suggested diff
             environmentOverrides: [
                 "PATH": "/opt/work/bin:/usr/bin:/bin",
                 "SHELL": "/opt/homebrew/bin/fish",
                 "WORK_TOKEN": "secret-token",
                 "CMUX_SOCKET": "__SOCKET_PATH__",
                 "CMUX_WORKSPACE_ID": workspaceID,
                 "CMUX_SURFACE_ID": surfaceID,
                 "CMUX_PANEL_ID": surfaceID,
                 "CMUX_TAB_ID": workspaceID,
                 "CMUX_PANE_ID": surfaceID,
                 "CMUX_RELAY_ID": "relay-id-abc",
                 "CMUX_RELAY_TOKEN": "relay-token-xyz",
+                "CMUX_TEST_SENTINEL": "must-not-forward",
             ]
         )
@@
         XCTAssertNil(initialEnv["CMUX_PANE_ID"])
         XCTAssertNil(initialEnv["CMUX_RELAY_ID"])
         XCTAssertNil(initialEnv["CMUX_RELAY_TOKEN"])
+        XCTAssertNil(initialEnv["CMUX_TEST_SENTINEL"])
🤖 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 `@cmuxTests/WorkspaceSSHTests.swift` around lines 300 - 326, Add a generic CMUX
sentinel to the test by including "CMUX_TEST_SENTINEL": "sentinel-value" in the
environmentOverrides passed to whatever creates captured, then after obtaining
initialEnv (captured["initial_env"] as? [String: String]) add an assertion
XCTAssertNil(initialEnv["CMUX_TEST_SENTINEL"]) so the test verifies that any
CMUX_* key (not just CMUX_RELAY_ID / CMUX_RELAY_TOKEN) is scrubbed; update the
diff around the environmentOverrides and the assertions block near
captured/initialEnv to include this sentinel check.
♻️ Duplicate comments (1)
cmuxTests/WorkspaceSSHTests.swift (1)

279-327: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert SSH_AUTH_SOCK in both env-forwarding tests.

These cases cover the forwarded caller environment, but they still do not assert SSH_AUTH_SOCK. That leaves a regression where agent forwarding drops out of workspace.create.initial_env while this suite stays green, even though sibling SSH tests treat that field as part of the contract.

Suggested diff
         let captured = try captureSSHWorkspaceCreateParams(
             arguments: ["ssh", "work.jumpgate-workspace"],
             environmentOverrides: [
                 "PATH": "/opt/work/bin:/usr/bin:/bin",
                 "SHELL": "/opt/homebrew/bin/fish",
+                "SSH_AUTH_SOCK": "/tmp/test-agent.sock",
                 "WORK_TOKEN": "secret-token",
             ]
         )
         let initialEnv = try XCTUnwrap(captured["initial_env"] as? [String: String])
         XCTAssertEqual(initialEnv["PATH"], "/opt/work/bin:/usr/bin:/bin")
         XCTAssertEqual(initialEnv["SHELL"], "/opt/homebrew/bin/fish")
+        XCTAssertEqual(initialEnv["SSH_AUTH_SOCK"], "/tmp/test-agent.sock")
         XCTAssertNil(initialEnv["WORK_TOKEN"])
@@
         let captured = try captureSSHWorkspaceCreateParams(
             arguments: ["ssh", "--inherit-env", "work.jumpgate-workspace"],
             environmentOverrides: [
                 "PATH": "/opt/work/bin:/usr/bin:/bin",
                 "SHELL": "/opt/homebrew/bin/fish",
+                "SSH_AUTH_SOCK": "/tmp/test-agent.sock",
                 "WORK_TOKEN": "secret-token",
                 "CMUX_SOCKET": "__SOCKET_PATH__",
                 "CMUX_WORKSPACE_ID": workspaceID,
@@
         let initialEnv = try XCTUnwrap(captured["initial_env"] as? [String: String])
         XCTAssertEqual(initialEnv["PATH"], "/opt/work/bin:/usr/bin:/bin")
         XCTAssertEqual(initialEnv["SHELL"], "/opt/homebrew/bin/fish")
+        XCTAssertEqual(initialEnv["SSH_AUTH_SOCK"], "/tmp/test-agent.sock")
         XCTAssertEqual(initialEnv["WORK_TOKEN"], "secret-token")
🤖 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 `@cmuxTests/WorkspaceSSHTests.swift` around lines 279 - 327, Add assertions for
SSH_AUTH_SOCK to both tests: in testSSHForwardsSafeCallerEnvironmentByDefault
and testSSHInheritEnvForwardsCallerEnvironmentAndScrubsCmuxContext, include
"SSH_AUTH_SOCK": "/tmp/ssh-auth-sock" (or a fixed test socket string) in the
environmentOverrides passed into captureSSHWorkspaceCreateParams, then assert
XCTAssertEqual(initialEnv["SSH_AUTH_SOCK"], "/tmp/ssh-auth-sock") after
unwrapping initialEnv so the forwarded agent socket is part of the tested
workspace.create.initial_env contract; references:
captureSSHWorkspaceCreateParams and initialEnv in both test functions.
🤖 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.

Outside diff comments:
In `@cmuxTests/WorkspaceSSHTests.swift`:
- Around line 300-326: Add a generic CMUX sentinel to the test by including
"CMUX_TEST_SENTINEL": "sentinel-value" in the environmentOverrides passed to
whatever creates captured, then after obtaining initialEnv
(captured["initial_env"] as? [String: String]) add an assertion
XCTAssertNil(initialEnv["CMUX_TEST_SENTINEL"]) so the test verifies that any
CMUX_* key (not just CMUX_RELAY_ID / CMUX_RELAY_TOKEN) is scrubbed; update the
diff around the environmentOverrides and the assertions block near
captured/initialEnv to include this sentinel check.

---

Duplicate comments:
In `@cmuxTests/WorkspaceSSHTests.swift`:
- Around line 279-327: Add assertions for SSH_AUTH_SOCK to both tests: in
testSSHForwardsSafeCallerEnvironmentByDefault and
testSSHInheritEnvForwardsCallerEnvironmentAndScrubsCmuxContext, include
"SSH_AUTH_SOCK": "/tmp/ssh-auth-sock" (or a fixed test socket string) in the
environmentOverrides passed into captureSSHWorkspaceCreateParams, then assert
XCTAssertEqual(initialEnv["SSH_AUTH_SOCK"], "/tmp/ssh-auth-sock") after
unwrapping initialEnv so the forwarded agent socket is part of the tested
workspace.create.initial_env contract; references:
captureSSHWorkspaceCreateParams and initialEnv in both test functions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59f3f8aa-a3e5-4705-959a-cdffa5f5deab

📥 Commits

Reviewing files that changed from the base of the PR and between 4564085 and 95169da.

📒 Files selected for processing (3)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • cmuxTests/WorkspaceSSHTests.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.

Caution

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

⚠️ Outside diff range comments (1)
cmuxTests/WorkspaceSSHTests.swift (1)

302-330: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add CMUX_BUNDLED_CLI_PATH to scrubbing test coverage.

The commit message explicitly mentions fixing scrubbing for CMUX_BUNDLED_CLI_PATH, but the test does not include this variable in environmentOverrides or the assertions. Consider adding it alongside the other CMUX_* keys to verify the fix is covered.

📝 Suggested addition
             "CMUX_TAB_ID": workspaceID,
             "CMUX_PANE_ID": surfaceID,
             "CMUX_RELAY_ID": "relay-id-abc",
             "CMUX_RELAY_TOKEN": "relay-token-xyz",
+            "CMUX_BUNDLED_CLI_PATH": "/some/path/to/cmux",
         ]
     )
     let initialEnv = try XCTUnwrap(captured["initial_env"] as? [String: String])
     // ... existing assertions ...
     XCTAssertNil(initialEnv["CMUX_RELAY_TOKEN"])
+    XCTAssertNil(initialEnv["CMUX_BUNDLED_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 `@cmuxTests/WorkspaceSSHTests.swift` around lines 302 - 330, The test is
missing coverage for CMUX_BUNDLED_CLI_PATH: add "CMUX_BUNDLED_CLI_PATH":
"/some/bundled/path" to the environmentOverrides dictionary used in the test,
and then add an XCTAssertNil(initialEnv["CMUX_BUNDLED_CLI_PATH"]) (alongside the
other XCTAssertNil checks) for the captured["initial_env"] assertion to verify
the scrubbing fix; locate the environmentOverrides and captured["initial_env"]
usages in the WorkspaceSSHTests test code to make the change.
🤖 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.

Outside diff comments:
In `@cmuxTests/WorkspaceSSHTests.swift`:
- Around line 302-330: The test is missing coverage for CMUX_BUNDLED_CLI_PATH:
add "CMUX_BUNDLED_CLI_PATH": "/some/bundled/path" to the environmentOverrides
dictionary used in the test, and then add an
XCTAssertNil(initialEnv["CMUX_BUNDLED_CLI_PATH"]) (alongside the other
XCTAssertNil checks) for the captured["initial_env"] assertion to verify the
scrubbing fix; locate the environmentOverrides and captured["initial_env"]
usages in the WorkspaceSSHTests test code to make the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01105a20-d130-40b8-868c-ef819a0737f0

📥 Commits

Reviewing files that changed from the base of the PR and between 95169da and 2582436.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • CLI/cmux.swift
  • cmuxTests/WorkspaceSSHTests.swift

@lawrencecchen
lawrencecchen dismissed coderabbitai[bot]’s stale review June 6, 2026 11:34

CodeRabbit now posts non-blocking comment reviews (request_changes_workflow=false, #5538).

@teamleaderleo teamleaderleo added area: remote cmux ssh, remote daemon, tunnels, device pairing S2: major A crash, hang, lost state, broken connection, or a regression on a path people use labels Sep 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: remote cmux ssh, remote daemon, tunnels, device pairing S2: major A crash, hang, lost state, broken connection, or a regression on a path people use

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants