One reusable OpenSSH connection per workspace - #523
Merged
Conversation
Reviewer's GuideThe PR makes terminal SSH launches reuse one OpenSSH master per workspace while preventing cross-run sharing when environment forwarding or agent state differs, gracefully falling back to direct connections when multiplexing is unsafe; it also fixes capture draining so stdout and stderr share a single post-exit grace period. Sequence diagram for reusable workspace SSH connectionssequenceDiagram
actor User
participant DL as dl
participant SSH as OpenSSH
participant Master as Workspace master
participant Container
User->>DL: dl workspace -- command
DL->>DL: Reuse::derive(workspace, SendEnv, SSH_AUTH_SOCK)
alt Multiplexed
DL->>SSH: command_args(ControlMaster=auto, ControlPath, ControlPersist=60)
SSH->>Master: Open or reuse control socket
Master->>Container: Execute command
Container-->>Master: Output
Master-->>DL: stdout and stderr
else Direct
DL->>SSH: command_args without control options
SSH->>Container: Execute command
Container-->>DL: stdout and stderr
end
DL-->>User: Command result
Sequence diagram for bounded stdout and stderr drainingsequenceDiagram
participant Runner
participant Child
participant StdoutDrain
participant StderrDrain
Runner->>StdoutDrain: Start drain thread
Runner->>StderrDrain: Start drain thread
Runner->>Child: Wait for child exit
Child-->>Runner: Exited
Runner->>Runner: Compute one DRAIN_GRACE deadline
Runner->>StdoutDrain: collect(deadline)
Runner->>StderrDrain: collect(deadline)
StdoutDrain-->>Runner: Captured stdout or timeout
StderrDrain-->>Runner: Captured stderr or timeout
Runner-->>Runner: Return captured result
Flow diagram for SSH control socket identity and fallbackflowchart TD
A[Build workspace SSH request] --> B[Derive key from host alias, SendEnv permit list, and SSH_AUTH_SOCK]
B --> C{Path fits and ssh-control directory is usable?}
C -->|Yes| D[Use hashed ControlPath]
D --> E[OpenSSH ControlMaster=auto]
E --> F[Reuse workspace master for 60 seconds]
C -->|No| G[Use Direct connection]
G --> H[Run command without control options]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
blooop
force-pushed
the
wayfinder/devlaunch-422
branch
from
August 29, 2026 19:56
0723db1 to
2194d15
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
blooop
force-pushed
the
wayfinder/devlaunch-422
branch
from
August 29, 2026 20:06
2194d15 to
8739e57
Compare
`dl <ws> -- <cmd>` at a terminal has gone into the container over OpenSSH since M3. It opened a fresh connection every time, and connection setup is nearly the whole cost of that trip: 2590ms to 3140ms fresh against 16ms to 28ms reused, on a rig at load average 21 where only the ratio travels (#390). Three `-o` options on an argv `clients/ssh.rs` already built are the whole mechanism. `ControlMaster=auto` means no pre-warm, so the spawn counts do not move; the argv does, and its pins say so. The socket is derived rather than configured, and its digest is the load-bearing part. A master filters `SendEnv` against its own permit list, in silence, at exit 0 (#389: `GOT=[]`, rc=0), so a master opened by a run with no token hands the next run an empty `GH_TOKEN` and an unauthenticated `gh` with nothing to say so. The digest covers the host alias, the permit list and `$SSH_AUTH_SOCK`, which makes that state unrepresentable instead of documented: a client whose list differs from the master's cannot find that master. Fields go in length-prefixed, so no two inputs encode alike. `Reuse::Multiplexed | Reuse::Direct` is a sum and not an `Option`, because `Direct` has real causes: a path too long for `sun_path`, or a directory dl cannot make. Both arms run the same command and differ in latency only. Everything that can go wrong ends at `Direct`, so a session that cannot be multiplexed is one that runs unmultiplexed, never one that fails. Closes #422.
`capture` computed the 500ms grace inside each `collect` and drained the two pipes serially, so one descendant holding both write ends was charged twice: a measured 1.008s where the bound says 500ms. One deadline is now taken once and handed to both calls. Nothing is lost by sharing it. Both drain threads start before the wait for the child does, so the second pipe has had the same grace to reach EOF in by the time it is asked. Decided on latency rather than correctness: a timed-out drain keeps the bytes it read either way. The held-pipe case the bound exists for is named in the code as git's ssh ControlMaster. #422 puts a ControlMaster on dl's hottest path, so the multi-pipe shape stops being occasional, which is why this lands here rather than being rediscovered later as a latency mystery. Closes #501.
blooop
force-pushed
the
wayfinder/devlaunch-422
branch
from
August 29, 2026 20:23
8739e57 to
ae71b57
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits, in this order.
1.
feat: one reusable OpenSSH connection per workspace(#422)dl <ws> -- <cmd>typed at a terminal has gone into the container over OpenSSH since M3, through the host aliasdevpod uppublishes. It opened a fresh connection every time, and connection setup is nearly the whole cost of that trip. Three-ooptions on an argvclients/ssh.rsalready built are the whole mechanism:ControlMaster=auto, a derivedControlPath,ControlPersist=60.automeans no pre-warm, so the spawn counts do not move; the argv does, and its pins say so.The socket is derived rather than configured, and its digest is the load-bearing part. A master filters
SendEnvagainst its own permit list, in silence, at exit 0 (#389 reproduced it:GOT=[], rc=0), so a master opened by a run with no token would hand the next run an emptyGH_TOKENand an unauthenticatedghwith nothing anywhere to say so. The digest covers the host alias, theSendEnvpermit list and$SSH_AUTH_SOCK, which makes that state unrepresentable rather than documented: a client whose list differs from the master's cannot find that master. Fields go in length-prefixed, so no two inputs encode alike. 16 hex characters of SHA-256, because aControlPathhas about 104 bytes to live in and an alias alone spends 30 of them.Reuse::Multiplexed(ControlSocket) | Reuse::Directis a sum and not anOption, becauseDirecthas real causes: a path too long forsun_path, a directory dl cannot make, or a%in the path. The length one is subtler than it looks and CI caught it:muxserver_listenbinds<ControlPath>.<16 characters>and renames it into place, so the budget is 17 bytes smaller thansun_path, and a check against the full 104 took the e2e suite down withunix_listener: path ... too long, exit 255, thirteen tests.SUN_PATHandLISTEN_SUFFIXare both named now. That last one is not hypothetical tidiness: OpenSSH runsControlPaththroughpercent_expandbefore it binds anything, and an unknown key there isfatal(), so a user whoseXDG_CACHE_HOMEheld a%would have had every terminal session die. The derived name is hex; everything above it is the user's, so a%anywhere in the path means do not multiplex. Both arms run the same command and differ in latency only. Everything that can go wrong ends atDirect, so a session that cannot be multiplexed is one that runs unmultiplexed, never one that fails. The sockets live under anssh-controlleaf of dl's cache directory (theLAUNCH_LOCK_DIRprecedent),0700, since anyone who can reach a master's socket is inside the container.2.
fix: DRAIN_GRACE bounds the drain, not each pipe(#501)capturecomputed the 500ms grace inside eachcollectand drained the two pipes serially, so one descendant holding both write ends was charged twice: a measured 1.008s where the bound says 500ms. One deadline is now taken once and handed to both calls. Nothing is lost by sharing it, since both drain threads start before the wait for the child does.lib.rs:489-491already named ssh's ControlMaster as the production case for a held pipe, and commit 1 puts one on dl's hottest path, which is why this lands here rather than being rediscovered later as a latency mystery.Tests
Test-first, and both red checks were run against the old behaviour rather than assumed.
two_send_env_permit_lists_never_derive_one_socket(the mandatory property, all pairs over six lists including["AB","C"]/["A","BC"], which collide without the length prefixes). Red without the permit list in the key.the_same_request_derives_the_same_socket_every_time,the_agent_socket_a_master_pins_is_part_of_its_key,two_workspaces_never_share_a_master.the_whole_argv_is_what_dl_hands_to_opensshanda_session_that_cannot_multiplex_carries_no_control_options_at_all, the two argv pins.a_socket_path_too_long_for_sun_path_leaves_the_session_direct,a_socket_directory_dl_cannot_make_leaves_the_session_directanda_cache_directory_with_a_percent_in_it_leaves_the_session_directandthe_room_openssh_takes_for_its_own_temporary_socket_is_counted_too, the fail-closed causes.the_socket_directory_is_this_users_alonefor the mode.a_run_with_a_token_and_a_run_without_never_share_a_masterandthe_control_socket_lives_under_devlaunchs_own_cache_directory, the same facts at the flow.two_pipes_one_descendant_holds_cost_one_grace_between_themfor capture pays DRAIN_GRACE once per pipe, so the drain bound is 1.0s not 0.5s #501: a realsetsid'd descendant holding both pipes, asserted at under one and a half graces and at over four fifths of one, so an EOF that arrived cannot make it pass having pinned nothing. Measured 1.008s before, ~0.5s after.cargo test --workspace,cargo clippy --locked --all-targets -- -D warnings,cargo fmt --checkandpytest testall green. No public API moved, so no snapshot regeneration.Closes #422. Closes #501.
🤖 Generated with Claude Code
Summary by Sourcery
Reuse OpenSSH connections per workspace and ensure captured command output drains within a single bounded grace period.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: