Repository navigation
One cold-path setup pass: fold the hostname into the probe's round trip, with per-stage outcomes #168
Description
Question
Collapse the cold path's two independent devpod ssh round trips into one setup pass: the hostname becomes a named stage in front of the shipped probe_script(), in the trip the probe already pays. Decided on #157.
Measured saving: ~1.92s per cold launch (composed one trip 1.64s vs two sequential trips 3.56s, medians of 5, real devpod against a real container — method on #157). A devpod ssh --command trip is ~1.73s and is ~99% connection/process setup, so the saving is a whole trip, near-exactly. Do not quote the map's ~0.45–0.56s here — that is devpod list / devpod status, a different command shape, and it understates an exec-into-container trip by ~3.5x.
probe → transfer is explicitly NOT merged. #157 refused it: merging either always streams the ~342MB payload on the very path the three-state probe exists to keep free, or gives the container back its own copy of the official-layout relation — the two-predicate drift #151 took three review rounds to remove. Re-opening that is a gate failure, not an optimization.
Behaviour
- One composed setup pass on the cold path, replacing the separate
setup_hostnametrip there. Composed on the host fromprobe_script()verbatim plus independent stage snippets. No probe logic is copied, re-expressed, or moved into the container. ProbeResult.parseand_is_official_claudeare unchanged.parsealready ignores every line not carryingPROBE_MARKand reads only thetools/versions/claudekeys, so stage output is already inert to it. The container gains stage-outcome lines and gains no opinions.- No
set -espanning stage boundaries. Each stage's failure is contained. (transfer_script'sset -euis correct for what it is — an all-or-nothing sequence — which is why the transfer is not a stage in this pass.) - The pass exits 0 in every state, preserving
_probe's existing discrimination: a non-zerodevpod sshmeans the transport failed, never that a stage did. - Per-stage outcomes, three-valued:
ok/failed(rc)/not reached(the absence of a line — what a truncated report or a mid-script death looks like). Model it as a sum type, not a bool or a sentinel; "never ran" and "ran fine" must not collapse. The host logs a warning naming any stage that is notok— that is the whole legibility claim and the reason this merge is allowed at all. - Not gated on
provisioning_disabled().ensure_toolsreturns before probing whenDEVLAUNCH_NO_TOOLSis set; if the hostname rides inside a pass that inherits that gate, disabling tools silently disables the hostname. Whether the pass runs and whether the tools stages do anything are two questions. - The warm path is untouched.
attach_workspacekeeps its standalonesetup_hostnamefor a warm attach — there is no pass there to fold into. - The "already named" signal is an explicit value carried from the launch that ran the pass to the attach that would otherwise repeat it. Never module-level mutable state, never a re-probe.
failedcounts as attempted — retrying standalone costs another ~1.7s to fail again. - Accepted behaviour change: a cold
dl <ws> -- cmdand a colddl <ws> upset no hostname today and will now set one for free, inside a trip already paid. This is an improvement — a container created by a one-shot launch is presently nameless until somebody attaches interactively. Say so in the CHANGELOG. - CHANGELOG
[Unreleased]entry. State the saving as measured, with the method, and do not restate the ~0.45–0.56s figure.
Seams to test at
test/test_devpod_spawn_counts.py — the round-trip regression seam. Verified drivable for the cold path already: workspace_up(..., workspace_identity=...) under the existing DevpodSpawns recorder records context options, up, the probe ssh, the transfer ssh, in order. No new fake is needed. The file has no cold pin at all today — every pin in it is warm — which is why this path's trip count can move silently.
- New cold-launch pin: exact argv sequence, one
ssh --commandcarrying the composed pass, then the transfer, and no separatesudo hostnamespawn. test_attaching_to_a_running_workspacestays green untouched — it still expectsstatus/ssh --command sudo hostname/ssh. It is the guard that the warm path was not touched; a diff that edits it has broken the scope line.- Mandatory isolation: stub
host_payloadin any cold pin. Observed: an unstubbed run takes the transfer arm only because that host has a claude and a gh to lend; a host with neither takes theabsentarm and spawnsprovision_scriptinstead, so the pin would assert a different sequence per developer. (test_starting_a_workspace_drops_the_snapshotreaches the realhost_payload()today and survives only because it asserts on the workspace list, not on spawns.)
test/unit/test_tools.py — the pass itself, in the kinds already present there:
- Script-string: the composed script contains
probe_script()verbatim (so a future edit cannot fork it); noset -espans stages. - Real-bash against scratch
$HOMEs (asTestHostPayloadand the probe's own tests do): a failing stage still lets the probe answer correctly in the same run, and the failing stage's marked line appears. This is directly observed to work — a composed run printeddevlaunch-probe stage hostname-failedfollowed by a correctdevlaunch-probe tools missing. not reached: a report truncated before a stage's line reads as not-reached, never asok.Runnerfake for trip counts per probe answer —provisionedpays exactly one trip (the pass) and nothing else.DEVLAUNCH_NO_TOOLSset → the hostname is still handled. Constraint 6 is invisible without this test.
Blocked by #167
Whether the hostname trip should exist in any form is being decided there: measured, sudo hostname cannot succeed in an unprivileged container (CAP_SYS_ADMIN is dropped by default; this repo's own devcontainer is privileged, which is why it works here). If #167 deletes or replaces it, this ticket is retired rather than built. Do not start before it settles.
Activity
blooop commentedon Aug 14, 2026
Spec amended by #167's resolution: (1) the hostname stage's failure reports at info level, not warning — it fails by design on every unprivileged image (CAP_SYS_ADMIN), so warn-by-name would fire on the majority case; other stages keep warn-by-name. (2) This PR also deletes
setup_hostnamefromattach_workspace— both halves land together, since deleting the warm trip before the stage exists regresses cold-path naming on privileged images. Warm interactive attach becomes 2 trips (status,ssh);test_attaching_to_a_running_workspacepins the new sequence,test_a_one_shot_command_skips_the_hostname_round_tripis affirmed unchanged.blooop commentedon Aug 14, 2026
breadcrumb: claimed and building. What lands, restated from the ticket plus #167's amendment:
One composed setup pass on the cold path, built on the host as stage snippets + the shipped probe script verbatim, sent in the trip the probe already pays. Each stage reports its own marked outcome line; the host reads three values per stage —
ok,failed(rc),not reached(the absence of a readable line) — as a sum type, and names any stage that is notok. The hostname is the pass's first and only stage today, reported at info level per the amendment, and it goes in front of the probe deliberately: the probe exits early when a tool is missing, so a stage behind it would readnot reachedon the commonest cold path. Noset -espans stages, the pass still exits 0 in every state so a non-zero trip still means the transport, and whether the pass runs is decoupled from whether the tools work runs — the tools opt-out no longer silently disables the hostname.probe → transferstays two trips; that merge is refused, not deferred.The amendment's other half lands with it: the standalone hostname trip is deleted from the warm attach, and with it the best-effort host-side helper that owned it — its job is the stage now, so leaving the function behind would leave a second, unreachable definition of "name this container".
Seams, as the ticket names them:
ssh --commandcarrying the composed pass, then the transfer, and no separate hostname spawn.host_payloadstubbed, mandatory — an unstubbed run picks its arm from whatever the developer's host has to lend. The warm interactive pin moves to its new two-trip sequence (status,ssh) rather than staying untouched: the amendment reversed that instruction. The one-shot pin is affirmed unchanged.set -espans stages), real bash against scratch$HOMEs (a failing stage still lets the probe answer correctly in the same run, and the failing stage names itself), stage-outcome reading (a report truncated before a stage's line readsnot reached, neverok), trip counts per probe answer through the existing runner fake, and the opt-out case: tools disabled, hostname still handled.tools;attachloses a round trip because that trip is deleted, not because it moved, and the stage's pinned spans say so.Trip counts change deliberately, both directions stated: cold launch 3 → 2, warm interactive attach 3 → 2.
blooop commentedon Aug 14, 2026
breadcrumb: built, and open as PR #206 — CI green on every matrix leg.
The pass is composed on the host from the shipped probe verbatim plus stage snippets, hostname first (the probe exits early on a missing tool, so a stage behind it would read
not reachedon the commonest cold path). Outcomes are a three-armed sum type read off marked lines, matched exhaustively, with not-reached as the absence of a readable line — so a garbled line is named rather than passed over. The host names everything that is notok; the hostname names itself at info, warning stays the default any other stage would get. The tools opt-out no longer reaches the pass.probe → transferuntouched.The warm half landed with it: the standalone naming helper is deleted rather than unwired, so there is no second, unreachable definition of "name this container" left behind, and the attach's interactive/one-shot branch is gone with the trip it existed to skip.
Measured on real containers under a scratch devpod home, before = current main, after = the branch, same host and image: warm interactive attach 4.65s → 2.48s (medians of 5), cold interactive launch 8.07s → 5.99s summed over the trips into the container (4 trips → 3). Both consistent with #157's composed-vs-sequential figure and with a trip being ~99% connection setup. The measurement ran on an unprivileged image, so every run reported the hostname stage as
failed 1— which is the majority case the info level was decided for, seen working.Two things the ticket asked for that are deliberately absent, both because #167's amendment removed what they were for: the "already named" signal carried from launch to attach has no reader once the warm trip is deleted, and "the warm path is untouched" is the instruction the amendment reversed. Nothing carries state between a launch and its attach.
Trip counts moved in both directions and are pinned as counts: cold launch 3 → 2 beyond the
up, warm interactive attach 3 → 2. The cold pins are new — the file had none at all, which is why this path could move silently.blooop commentedon Aug 14, 2026
gist: wf-review verdict on PR #206: Request changes — code meets every spec line (both axes), but README:793 still documents the deleted round-trip-skip (blocking under the README-in-step rule) plus three same-pass doc drifts. Fix agent dispatched on the same branch.
docs: the setup pass in the README, and the comments it left stale