Cut the time from launch to a working session - #138
Merged
Merged
Conversation
Reviewer's GuideThis PR reduces launch-to-session latency by replacing global workspace listing with targeted status checks, introducing host-side tooling transfer, adding a non-attaching Sequence diagram for ensure_tools host lending and fallback flowsequenceDiagram
participant Caller as workspace_up
participant Tools as tools.ensure_tools
participant Devpod as run_devpod
Caller->>Tools: ensure_tools(workspace, run_devpod)
Tools->>Devpod: _probe via run_devpod("ssh", --command "bash -lc checks", capture=True)
alt tools already present
Devpod-->>Tools: returncode == 0
Tools-->>Caller: True
else tools missing
Devpod-->>Tools: returncode != 0
Tools->>Tools: host_payload()
alt host_payload() returns payload
Tools->>Devpod: _transfer via run_devpod("ssh", --command transfer_script, stdin_file=tar)
alt transfer succeeded
Devpod-->>Tools: returncode == 0
Tools-->>Caller: True
else transfer failed
Devpod-->>Tools: returncode != 0
Tools->>Tools: provision_script(tools)
Tools->>Devpod: run_devpod("ssh", --command "bash -lc provision_script")
Devpod-->>Tools: result
Tools-->>Caller: result.returncode == 0
end
else host_payload() is None
Tools->>Tools: provision_script(tools)
Tools->>Devpod: run_devpod("ssh", --command "bash -lc provision_script")
Devpod-->>Tools: result
Tools-->>Caller: result.returncode == 0
end
end
Sequence diagram for serialized workspace_up with per-workspace locksequenceDiagram
actor User
participant CLI as _run_cli
participant Up as workspace_up
participant Lock as hold_lock
participant Devpod as run_devpod
participant Tools as tools.ensure_tools
User->>CLI: dl <workspace> up
CLI->>CLI: get_workspace_state(workspace_id) -> known_state
alt known_state == "Running" and custom_id is None
CLI->>Tools: ensure_tools(workspace_id, run_devpod)
Tools-->>CLI: True/False
CLI-->>User: return 0 (already running)
else need up/create
CLI->>Up: workspace_up(workspace_spec, workspace_identity=workspace_id)
Up->>Lock: hold_lock(_launch_lock_path(identity), waiting_note)
alt lock contended (waited == True)
Lock-->>Up: waited = True
Up->>CLI: get_workspace_state(identity)
alt state == "Running" and not (ide or recreate or reset or devcontainer)
Up->>CLI: invalidate_workspace_list_cache()
Up->>Tools: ensure_tools(identity, run_devpod)
Tools-->>Up: True/False
Up-->>CLI: CompletedProcess(returncode=0)
CLI-->>User: return 0
else needs devpod up
Up->>Devpod: run_devpod(["up", workspace_spec, ...])
Devpod-->>Up: result
end
else lock not contended
Lock-->>Up: waited = False
Up->>Devpod: run_devpod(["up", workspace_spec, ...])
Devpod-->>Up: result
end
Up->>CLI: invalidate_workspace_list_cache()
alt result.returncode == 0 and identity
Up->>Tools: ensure_tools(identity, run_devpod)
Tools-->>Up: True/False
end
Up-->>CLI: result
CLI->>CLI: update_cache_background(force=True)
CLI-->>User: return result.returncode
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Three costs sat between `dl <spec>` and a usable container, and none of them were doing anything. **A launch asked devpod the same question twice.** Every workspace command opened with `devpod list --output json` purely to test whether devpod knew this workspace, then asked `devpod status` about that same workspace moments later. One `status` answers both, so the listing is gone from the path: `dl owner/repo@branch -- cmd` on a running workspace is two devpod spawns rather than three. Measured on the reference machine, ~0.4-0.5s off every launch, and it no longer grows with the number of workspaces the machine has. A bare name gets a second opinion before being refused. `status` consults the provider while `list` reads devpod's own records, so a workspace whose provider is broken still lists and cannot be described -- and that is the workspace somebody is about to `dl <ws> rm`. **A cold container downloaded tools the host already had.** Provisioning curl-installed pixi and two packages inside every fresh container, and `claude-shim` then pulled ~285MB from GCS. The host almost always has both already and the container is one pipe away on the same disk, so they are streamed in as a tar over the `devpod ssh` channel dl already holds: 342MB in 5.1s here, checksum-verified, both binaries running in the container. Nothing lands until it has been proved to run there. The tar unpacks into a staging directory, both binaries are run once, and only then are they moved into place, symlinked and put on the login PATH. The host's claude is dynamically linked, so a musl or older-glibc container fails that check routinely -- and unpacking straight into $HOME left a broken symlink and a PATH edit behind when it did, which the network fallback then read through `command -v` as "already installed". The workspace ended up with a claude that could never run, reported as success forever. **Two `up`s of one workspace raced.** `dl <spec> up` is new -- start without attaching, so a caller can warm a container before a user arrives -- and that makes concurrent `up`s of one workspace ordinary rather than exotic. They serialize on a per-workspace lock; a waiter re-checks state and skips an `up` the sibling already did, but never for a call wanting something the sibling cannot have done (an IDE, a recreate, a reset, a --devcontainer variant), and it still checks the tools, because "Running" says the sibling's `up` returned and not that its install did. A lock that cannot be taken is not worth failing a launch over. Also: `devpod context options` is cached against devpod's own config file rather than re-read before every `up`; the tools probe no longer surfaces its everyday "not installed yet" answer as a red devpod `fatal`; `.dockerignore` excludes `.pixi` at any depth, which is what was breaking the e2e container build; and `validate_workspace_spec` is gone with the listing it existed to check against.
blooop
force-pushed
the
perf/launch-latency
branch
from
August 8, 2026 21:50
830c2bd to
6abc36f
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #138 +/- ##
==========================================
- Coverage 93.79% 93.03% -0.76%
==========================================
Files 20 20
Lines 2239 2354 +115
==========================================
+ Hits 2100 2190 +90
- Misses 139 164 +25
🚀 New features to boost your workflow:
|
This was referenced Aug 8, 2026
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.
Three costs sat between
dl <spec>and a usable container. Measured on this machine throughout.A launch asked devpod the same question twice
Every workspace command opened with
devpod list --output jsonpurely to test whether devpod knew this workspace, then askeddevpod statusabout that same workspace moments later. Onestatusanswers both.dl owner/repo@branch -- cmdon a running workspace — the shape wayfinder hands every agent launch — is now two devpod spawns rather than three. Measured ~3.4s → ~3.0s per launch, and it no longer grows with the number of workspaces on the machine.A bare name gets a second opinion before being refused:
statusconsults the provider whilelistreads devpod's own records, so a workspace whose provider is broken still lists and cannot be described — and that is exactly the workspace somebody is about todl <ws> rm. Refusing on the status alone would be a wrong diagnosis and a refusal of the command that fixes it.A cold container downloaded tools the host already had
Provisioning curl-installed pixi and two packages inside every fresh container, and
claude-shimthen pulled ~285MB from GCS — tens of seconds to minutes, per container, on the critical path. The host almost always has both already and the container is one pipe away on the same disk, so they are streamed in as a tar over thedevpod sshchannel dl already holds.342MB in 5.1s, checksum-verified, both binaries running in the container.
Nothing lands until it has been proved to run there: the tar unpacks into a staging directory, both binaries are run once, and only then are they moved into place, symlinked and put on the login
PATH, with a trap removing the staging directory either way. This ordering is load-bearing — the host'sclaudeis dynamically linked, so a musl or older-glibc container fails that check routinely, and unpacking straight into$HOMEleft a broken symlink and aPATHedit behind when it did. The network fallback then read those throughcommand -vas "already installed", installed nothing, and reported success — leaving a workspace with aclaudethat could never run, forever. Reproduced, then fixed and re-verified against the same scenario.Two
ups of one workspace raceddl <spec> upis new: start or create without attaching, so a caller can warm a container before a user arrives. That makes concurrentups of one workspace ordinary rather than exotic, and twodevpod upagainst one id is not a race devpod promises to survive.They serialize on a per-workspace lock. A waiter re-checks state and skips an
upthe sibling already did — but never for a call wanting something the sibling cannot have done (an IDE, a recreate, a reset, a--devcontainervariant), and it still checks the tools, becauseRunningsays the sibling'supreturned and not that its install did. A lock that cannot be taken is logged and skipped rather than failing a launch.Also
devpod context optionsis cached against devpod's own config file rather than re-read before everyup— sodevpod context use <other>invalidates it instead of serving the previous context's dotfiles settings for an hour.fatal ... Process exited with status 1..dockerignoreexcludes.pixiat any depth. A leftover worktree under.claude/worktrees/put the very symlink that file exists to exclude back into the build context, which was breaking the e2e container build (pre-existing).validate_workspace_specis gone with the listing it existed to check against.Verification
tyand pylint (10.00/10) clean.🤖 Generated with Claude Code
Summary by Sourcery
Reduce devpod spawn count and serialize workspace launches to cut time from
dl <spec>to a usable session, while adding host-based tool provisioning and a non-attachingupcommand.New Features:
dl <workspace> upto start or create a workspace without attaching, supporting background prewarming of containers.Bug Fixes:
claudesymlink or PATH edits behind and correctly fall back to network installation, avoiding unusable workspaces that appear provisioned..dockerignoreso.pixidirectories are excluded at any depth, avoiding accidental inclusion of pixi environments from nested worktrees that broke e2e container builds.validate_workspace_specusage and adjust spec rejection paths so truly unknown workspaces are refused without extra devpod spawns while still allowing cleanup of workspaces devpod lists but cannot describe.Enhancements:
devpod listusage with targeteddevpod statuscalls during CLI spec resolution and fast-attach, lowering launch overhead and making behavior independent of workspace count.devpod context optionson disk with TTL and config-file invalidation to avoid repeated devpod round trips before everyup.workspace_upcalls per workspace using a lock, including contention-aware behavior that skips redundantupwhen a sibling already started the workspace while still ensuring tools installation.claudeandghbinaries into the container overdevpod ssh, with staged verification and a fallback to the existing network-based pixi/pixi-global installs.get_workspace_stateand a second-opinion listing only for bare names that may represent broken-provider workspaces.upsubcommand and refreshed workspace lifecycle semantics.Documentation:
dl <workspace> upcommand, the reduced devpod spawn strategy, and host-based tool lending behavior, including performance characteristics and edge cases.Tests:
workspace_up, including contention scenarios and failure to take locks.devpod status-based spec resolution,upsubcommand, and changed workspace state handling.validate_workspace_specandget_workspace_idsassumptions, pinning behavior onget_workspace_stateand new error paths instead.