Skip to content

Add setting to enable reusing user's existing control master#12465

Merged
kevinyang372 merged 3 commits into
masterfrom
kevin/add-setting-to-reuse-control-master
Jun 11, 2026
Merged

Add setting to enable reusing user's existing control master#12465
kevinyang372 merged 3 commits into
masterfrom
kevin/add-setting-to-reuse-control-master

Conversation

@kevinyang372

@kevinyang372 kevinyang372 commented Jun 10, 2026

Copy link
Copy Markdown
Member

Description

Problem

Some users manage their own SSH ControlMaster for a host: their ssh config sets ControlMaster auto + ControlPath, and that master owns shared state — in the reported case, a reverse-forwarded (RemoteForward) socket used by all of their SSH clients. Warp's SSH wrapper always forces -o ControlMaster=yes -o ControlPath=~/.ssh/<session_id> on warpified sessions, so connecting from Warp creates a second, competing master for the same host. Only one connection can bind the reverse forward, so the two masters race over it and the user's tooling becomes unreliable (Warning: remote port forwarding failed ...).

Fix

Behind a new opt-in setting (warpify.ssh.reuse_existing_control_master, default off; toggleable from the Warpify settings page and the Command Palette), the SSH wrapper now looks for a live master the user already runs for the destination before creating its own:

  1. ssh -G "$@" resolves the user's effective ControlPath, with tokens (%h/%p/%r/%C) expanded into a literal path.
  2. ssh -O check verifies a master is actually alive on that socket. Both probes are local-only commands.
  3. If alive, the foreground ssh runs with ControlMaster=no against the user's socket — Warp becomes just another client of the user's master, so there is no second master and no forward conflict. All multiplexed traffic (remote-server proxy, scp, generators) flows through the same socket unchanged.
  4. On any miss (no ControlPath configured, dead master, path characters we can't safely embed, older ssh), the wrapper falls back to today's behavior exactly.

Master ownership is threaded end-to-end so teardown is safe: the SSH DCS hook reports a new external_master field, plumbed through IsLegacySSHSessionSshTransport → a new ControlPath enum (WarpManaged / UserOwned / None) on the remote-server session state. Session teardown only runs ssh -O exit against WarpManaged masters — Warp never tears down a master the user owns.

Alternatives considered

  • Stop binding forwards from Warp's master (e.g. force ClearAllForwardings=yes in the wrapper): prevents this specific conflict but still leaves two masters per host, doesn't address non-forward conflicts, and silently drops forwards users do want in Warp sessions.
  • Manual "attach to this socket" setting (user supplies a ControlPath per host): avoids discovery entirely, but requires per-host configuration and can't express tokenized ControlPaths (%C, %r@%h:%p), which are the recommended and most common setup.
  • Destination-aware transport refactor: thread the user's real ssh destination through the client so secondary commands name the host instead of placeholder@placeholder. Most general, but a much larger change — and unnecessary, since ssh -G already yields a literal expanded socket path that the existing socket-only transport consumes as-is.
  • No product change (user-side workaround): users can move RemoteForward to a dedicated master started outside Warp. Works, but pushes config surgery onto every affected user and leaves the surprising two-master behavior in place.

Rollout

Default off for safety: reusing a master couples Warp's session lifetime to the user's master and shares its MaxSessions channel budget, which existing multiplexing users may not expect. Once validated more broadly we can flip the default and keep the setting as an escape hatch.

Linked Issue

No linked issue — this addresses a customer support request about the SSH wrapper's forced ControlMaster conflicting with user-managed masters.

  • The linked issue is labeled ready-to-spec or ready-to-implement.
  • Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes).

Testing

  • I have manually tested my changes locally with ./script/run

Automated

  • Unit tests for SSH DCS hook parsing of the new external_master field, including the default (false) for hooks emitted by older bootstrap scripts.
  • Updated transport/session tests for the new SshTransport ownership parameter; cargo nextest passes for the affected crates; ./script/format + clippy clean.
  • Syntax-checked all three modified bootstrap scripts (bash -n, zsh -n, fish --no-execute).

Manual E2E (./script/run against a remote devbox configured like the customer: ControlMaster auto, tokenized ControlPath, RemoteForward):

  • With a user-started master running: Warp attached to it — no new Warp socket in ~/.ssh, no port-forward failure, session fully warpified, remote-server features work over the reused socket.
  • After exiting the Warp session: ssh -O check confirms the user's master is still running (Warp did not tear it down) and the reverse forward stays bound.
  • Fallback: with no live master, the wrapper creates a Warp-owned master exactly as before and cleans it up on exit.
  • Regression: a host with no ControlPath configured behaves identically to today.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Conversation | Options plan

CHANGELOG-IMPROVEMENT: Added an opt-in setting (warpify.ssh.reuse_existing_control_master) that makes Warp's SSH wrapper attach to an existing SSH ControlMaster for the destination host instead of always creating its own, preserving user-managed masters and their port forwards.

Co-Authored-By: Oz oz-agent@warp.dev

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@cla-bot cla-bot Bot added the cla-signed label Jun 10, 2026
@kevinyang372 kevinyang372 requested a review from alokedesai June 10, 2026 18:58
Comment thread app/src/remote_server/ssh_transport.rs Outdated

// Reuse-ControlMaster only applies to the legacy (ControlMaster)
// SSH wrapper, which is bypassed while the tmux wrapper is in use.
if !should_prompt_ssh_tmux_wrapper {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this setting meant to be permanent or just a temporary state before we enable it for everyone? If the latter, i'd advocate for a simpler approach where we only make the setting available in the file and don't expose anything in the UI

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we should have a permanent setting in case user actually want Warp to always create a master connection. There are actual use cases for this imo (e.g. user has limited number of control paths per master connection)

Comment thread app/src/terminal/model/session.rs Outdated
Comment thread crates/remote_server/src/ssh.rs Outdated
Comment thread app/assets/bundled/bootstrap/bash_body.sh Outdated
Comment thread app/assets/bundled/bootstrap/bash_body.sh Outdated
@kevinyang372 kevinyang372 force-pushed the kevin/add-setting-to-reuse-control-master branch from 5449277 to 730a239 Compare June 11, 2026 21:42
@kevinyang372 kevinyang372 merged commit 0d24d2c into master Jun 11, 2026
26 checks passed
@kevinyang372 kevinyang372 deleted the kevin/add-setting-to-reuse-control-master branch June 11, 2026 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants