You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
dl <path> dotfiles has no liveness pin, and the status call there is not a duplicate #408
Build the one duplicate removal that The pre-updevpod status call costs 0.43-0.74s on every launch
found while refusing everything else. Read that ticket's resolution comment
first — it is the spec, and it is mostly a record of what not to do here.
flows/launch.rs::run_dotfiles re-asks devpod for state that Placement
already carries. At flows/launch.rs:2843 it calls lifecycle::workspace_state(...).is_ok_and(ContainerState::is_running) where placement.is_running() gives the same answer in all three arms. That is one devpod status round trip — measured ~0.57s — spent asking a question this
launch has already answered.
The slice:
Replace the workspace_state call in run_dotfiles with placement.is_running().
Confirm the three arms genuinely agree before changing it; if they do not, that
is a finding and the ticket stops rather than the behaviour changing.
Add devpod statuscount pins per launch shape at the existing
recorded-call seam (flows/launch.rs unit tests plus rust/dl/tests/launch.rs).
The red test is dotfiles-warm = 1 before the change. Afterwards 0 is a
failure for every warm shape — the point of the pin is that the launch must
keep making exactly one live liveness observation, so a future change that
removes the remaining status call gets caught rather than celebrated.
CHANGELOG line.
No new module, no cache, no type change.
What this ticket must not do
#393 refused three cheaper-looking things, each on measurement, and this build
must not quietly reintroduce any of them:
No cache of container liveness. A container stopped by anything but devpod
itself leaves $DEVPOD_HOME byte-identical, mtimes included. There is no key.
No "try the attach and fall back".devpod ssh <ws> --command against a Stopped workspace does not fail: it starts the container and exits 0.
A trip can silently perform an up.
No always-up. 2.12-2.23s against status's 0.569s, and it rewrites the
verdict anchor every launch.
No Remembered arm on ContainerState, and no stale/cached_at/Option
beside it. The invariant is "one observation of liveness per launch, an
observation not a memory"; Placement::Known { state } already enforces it.
The provider fast path (docker inspect, 0.018s) is not this ticket's — it
belongs to #390 and carries a
type constraint recorded there.
Notes for the builder
This clone may be behind origin/main; cut the branch from origin/main.
Other agents may be active — use a git worktree of your own.
Guards: cargo test --workspace, cargo clippy --locked --all-targets -- -D warnings, cargo fmt --check in rust/. No em or en dashes in README or docs/ pages
(test/test_docs_prose.py).
finding — the ticket stops at step 1's caveat: the three arms do not agree, and the arm
that disagrees makes the change a regression rather than a saving.
placement.is_running() and lifecycle::workspace_state(...).is_ok_and(ContainerState::is_running)
give different answers for Placement::Creating, and the difference is not in the arm's
own reading. It is in which of the two ways a Creating is built:
how the Creating was built
devpod asked during placement?
is_running()
the workspace_state re-ask
place_triple's cold path (prepare)
yes, and it denied the id
false
denies again — agrees
Plan::Creatable (a path or a git source)
no. nothing at all
false
asks for the first time, and can answer Running — disagrees
Plan::Creatable's own doc comment is where the trap is written down:
A path or a git source devpod clones itself. devpod is given source and --id workspace_id,
and nothing is asked of devpod about it — that is Python's behaviour rather than an
oversight: everything creatable goes through up, which is idempotent for a workspace
devpod already has.
So for dl <path> dotfiles the workspace_state call in run_dotfiles is not a duplicate.
It is the only liveness observation the command makes, and #393's invariant — one
observation of liveness per launch, an observation not a memory — is what it is satisfying. Placement::Creating carries no memory of liveness to reuse, because nothing ever looked.
Reproduced at the binary boundary
rust/dl/tests/launch.rs, --warm world, spec = the path of the running workspace's own
clone so the derived leaf is the id devpod knows (devlaunch-main-zovomobo):
It is refusal 3 (always-up) reintroduced, on exactly the shape where the status
call was load-bearing. Arithmetic with The pre-up devpod status call costs 0.43-0.74s on every launch #393's and Re-baseline launch latency on the Rust build #388's own figures: minus one status
(0.569s), plus one up-on-running (2.12-2.23s), plus two ssh trips (2.07-2.33s each) —
about +5.7s to +6.4s on this shape, and a workspace_result.json rewrite that moves verdict_cache's anchor on every dl <path> dotfiles.
The up it starts making is a fully identified one — --init-env DEVLAUNCH_WORKSPACE_ID, launch lock, tools — where run_dotfiles's doc comment says
this verb's up passes no identity and dotfiles_starts_a_stopped_workspace_first_and_says_so
pins devpod up {MAIN} --ide none {PIXI} for it. That pin still passes, because it uses
a named spec; the path-spec shape has no pin at all, which is why this was invisible.
Listed — is_running() is false; the re-ask is a second workspace_state against
the devpod whose first one just failed, so it fails again and reads false. Agrees except
under a devpod that answers differently between two calls seconds apart, which is the same
window as above.
The duplicate is real for the named-spec shape (dl <ws> dotfiles, dl owner/repo dotfiles),
which is the shape #393 measured: there Placement::Known carries the observation and run_dotfiles asks again for nothing, and the existing golden dotfiles_refreshes_a_running_workspace_without_bringing_anything_up pins the two status
calls with a comment explaining them. It is not real for the path-and-git-source shape,
where the Creating arm carries no observation because none was ever made.
So the slice as written is not one substitution. It needs a decision this build stage should
not make alone, and the shapes are:
Narrow the substitution to where the observation exists. Read the Placement when it
has one and keep the live ask when it does not — which is a match on the arm rather than
a call to is_running(), since is_running()'s false is deliberately overloaded
("nothing said it was running, which is not the same as saying it is not"). No new type,
no new arm; the honest reading is that Creating cannot answer this question and the
caller has to ask.
Or ask once, earlier, for the creatable shape too — which is a change to place,
not to run_dotfiles, and puts a status round trip on the dl <path> attach path that
deliberately has none today.
Or drop the slice.The pre-up devpod status call costs 0.43-0.74s on every launch #393 already says this ticket does not move the headline number;
if the win is only the named-spec shape, it is 0.57s off a verb nobody waits on in a
loop, against a call site whose overloaded false is easy to get wrong twice.
The pin table also needs a row it does not have: dl <path> dotfiles, warm = 1, and
that 1 is run_dotfiles's own call. Any pin table without that row cannot catch this.
Behaviour is unchanged and nothing is pushed: the probe test and the substitution were both
reverted, and the worktree is byte-identical to origin/main at 57955a3. Guards there: cargo fmt --check and cargo clippy --locked --all-targets -- -D warnings clean; cargo test --workspace green apart from one pre-existing flake, aid's a_pasted_multi_line_prompt_arrives_whole_rather_than_leaking, which fails only when the
full workspace run loads the machine and passes on its own and with its whole target
(cargo test -p aid --test interactive, 7 passed). It is a real pty timing race on origin/main and belongs to nothing here.
changed the title [-]dl <ws> dotfiles reads the state it was given, not devpod again[/-][+]dl <path> dotfiles has no liveness pin, and the status call there is not a duplicate[/+]on Aug 24, 2026
breadcrumb: retyped wayfinder:build to wayfinder:grilling by the manager, and the title changed with it. The build attempt above did the right thing by stopping: what looked like a slice was a question wearing a slice's clothes, and the answer decides whether there is any code here at all.
The question this ticket now asks, from the finding above:
Which of the three ways forward — narrow the substitution to a match on the arm (Known/Listed only, keeping the ask for Creating), ask once earlier for the creatable shape, or drop the slice entirely. Note what the original slice's whole justification was: one duplicate removed, worth 0.57s. Any option that adds a match arm to save 0.57s owes principle 2 an argument it did not have to make when it was a deletion.
The missing pin row is worth having whichever way (1) goes, and may be the only thing worth shipping: the path-spec dotfiles shape has no liveness pin, which is exactly why this was invisible. The pre-up devpod status call costs 0.43-0.74s on every launch #393's pin table needs that row.
Map #139's entry for #393 has been corrected to record the falsification. The refusals #393 established stand unchanged; only its "one duplicate" removal does not.
breadcrumb: the arm's doc comment is false for half of its values, and that is the defect rather than a consequence of it.
Placement::Creating (flows/launch.rs:2134) says, of itself:
This launch may have to create it: source is what devpod is given and workspace_id is the --id. Nothing has been asked of devpod about it.
True of the value place builds at launch.rs:2576 from Plan::Creatable. False of the value prepare builds at launch.rs:2341, which is reachable only through Resolution::Cold, which is reachable only when lifecycle::resolve_known_workspace asked devpod status for the derived id — and, where a record named a different one, for that too — and devpod denied it (KnownWorkspace::Unknown, lifecycle.rs:653).
So one sentence, one arm, both true and false depending on which of two lines built the value. #393 read the true half of it and wrote a decision on that reading. Nothing downstream can tell the halves apart, because the type does not spell the difference.
breadcrumb: two more shapes measured at the seam the finding used — rust/dl/tests/launch.rs, --warm world, the devpod_shim.py call log. Probes reverted; the worktree is byte-identical to origin/main at 57955a3.
dl blooop/devlaunch@cold dotfiles — a cold triple, so the Creating that prepare built:
Two status calls, both denials of the same id, the second bought and thrown away. So the finding's "agrees" row understates this arm: for a Creating that prepare built the re-ask is not merely consistent, it is redundant, and removable on its own evidence — devpod denied the id moments earlier, and the one case where that could have changed underneath is a sibling create, which up_under_stage re-asks about anyway on the other side of its wait (launch.rs:1091-1104) for a Naming::Create request, which is what this verb passes for this arm.
dl {ROOT}/cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo -- true — a path spec whose leaf is the id the shim knows, workspace Running:
Zero status calls, and a fully identified up against a container that is already running, plus both setup trips. That is refusal 3 — always-up — shipping today, unpinned, on the shape this map is named after. It is the same defect as the dotfiles one seen from the other side: the arm cannot say "running", so this shape never asks and always ups.
Question 1: none of the three as written. The substitution is narrowed to where an observation exists — that much of shape one is right — but it is narrowed by splitting the arm that carries two meanings, not by matching around it at one call site. run_dotfiles then matches a Placement whose arms each mean one thing, and the special case disappears because it stops being special: an arm that never asked devpod anything is an arm whose reader has to ask.
Question 2: yes, and it ships first. The pin rows are the guard whose absence hid this, they are green on origin/main as written, and they land before anything moves.
Principle 1 decided it, over principle 2, on the evidence of this ticket: principle 3 supplies the shape, and the reason the shape is worth its arm is that the one-arm version has already cost a falsified decision and a reverted build.
Question 1: the options
Option A — narrow at the call site: match in run_dotfiles, keep the ask for Creating
The finding's own first shape. It is correct behaviour and it is three lines. What it leaves standing is the sentence in the first breadcrumb: Placement::Creating's doc comment is true of one construction and false of the other, and is_running()'s false still means two things with a comment to explain which. The next reader of either gets what #393 got. Principle 1 outranks principle 2, and the cost is not hypothetical — it is this issue.
It also leaves the redundancy the second breadcrumb measured: a Creating that prepare built asks devpod a question devpod answered moments ago, twice on the cold dotfiles shape, and Option A pays that forever because it cannot tell that arm from the other one.
Option B — ask once, earlier, for the creatable shape
Not refused. Unblocked, and ticketed separately — see the last section. Two reasons it is not this ticket's answer:
On its own it saves nothing. The dl <path> dotfiles shape makes exactly one status call today (measured: 1). Moving it from run_dotfiles to place still makes one, and puts 0.57s on every other path-spec verb that has none. Strictly worse.
It only pays if the fast attach comes with it, and that is a behaviour change on the dl <path> launch path with an open question of its own (the derived id is a path leaf). Its upside is ten times this ticket's, which is exactly why it should not ride along inside it.
Option C — drop the slice, ship the pin
Honest, and it was the safe answer before the arm was measured. It forgoes 0.57s on three shapes for nothing, and — worse — it leaves the type saying something false, which is the thing that actually cost this map a cycle.
Option D — split the arm. Winner.
Placement::Creating becomes two arms, because it is two states:
asked, and devpod denied the id — prepare's value, reached only through Resolution::Cold. Never warm, and the denial is the reason rather than an assumption.
nothing asked — Plan::Creatable's value. Not "not running": unobserved. devpod may well already have this id; up is idempotent and is what settles it today.
Then run_dotfiles matches all four arms and the Creatable arm is the only one that asks — which is not a special case but the arm's meaning. And the arm that prepare built stops paying for its second denial.
The type shape
pubenumPlacement{/// devpod knows this workspace and reported this state.Known{workspace_id:String,state:ContainerState},/// devpod lists it and could not describe it.Listed{workspace_id:String},/// devpod was asked for this id and denied it, and the host has prepared the/// clone: this launch is the create.Creating{workspace_id:String,source:String},/// A path or a git source. **Nothing has been asked of devpod**, so this/// launch does not know whether devpod already has `workspace_id`. Not "not/// running" — unobserved.Creatable{workspace_id:String,source:String},}
workspace_id(), source() and naming() treat the new arm exactly as they treat Creating (Naming::Create, source positional), so nothing about what devpod is handed changes.
is_running() goes, and is replaced by the positive question it was the only honest answer to:
/// Whether this launch may attach without an `up`.////// Only `Known` can say yes. The other three have no positive answer to give —/// devpod could not describe it, devpod denied it, or nothing has asked — and/// this is the question where all three of those correctly mean no.pub(crate)fnattachable(&self) -> bool{matchself{Self::Known{ state, .. } => state.is_running(),Self::Listed{ .. } | Self::Creating{ .. } | Self::Creatable{ .. } => false,}}
The rename is the load-bearing half. is_running() promised to answer both directions and could only answer one; the comment apologising for that ("Nothing said it was running, which is not the same as saying it is not") is the smell, and a predicate named for the question it actually answers does not need it. run_attach and run_up_verb read attachable() unchanged in meaning — for both, "no positive answer" and "not running" lead to the same up.
run_dotfiles is the one site where they do not lead to the same place, so it does not get a projection at all:
let running = match placement {Placement::Known{ state, .. } => state.is_running(),// devpod could not describe it, or denied this id moments ago: `up` either way.Placement::Listed{ .. } | Placement::Creating{ .. } => false,// Nothing has asked. This is the launch's one liveness observation, not a// second one.Placement::Creatable{ workspace_id, .. } => {
lifecycle::workspace_state(self.context.runner(), workspace_id).as_ref().is_ok_and(ContainerState::is_running)}};
What is still not added: no cache, no TTL, no cached_at, no Option<ContainerState>, and no Remembered — #393's deliberate absence stands. Creatable is the opposite of Remembered: Remembered would carry a liveness reading from the past, and Creatable carries none at all and says so. ContainerState is untouched, five arms, Unknown(String) kept.
Placement is pub but is not in devlaunch_core's prelude (lib.rs:127 exports Launch and LaunchVerb only) and appears nowhere outside flows/launch.rs — 14 sites, one file, all compiler-flagged. The public-API snapshot is unaffected.
What it costs
One more arm, 4 where there were 3, to save 0.57s on shapes nobody waits on in a loop. That is the trade principle 2 objects to, and the answer is that the arm is not bought with the 0.57s: the two states already exist in the program and were both spelled Creating. Naming a distinction the program already makes is what principle 3 is for, and principle 1 is what says a type that states something false is worth an arm to fix.
dl <path> dotfiles keeps its 0.57s, and correctly: that call is the command's only liveness observation.
Removed round trips, all measured at the shim seam: one off dl <ws> dotfiles, one off dl owner/repo@<cold> dotfiles, one off the Listed dotfiles shape (by inspection — the second status is against the devpod whose first one just failed). No shape gains one.
Question 2: the pins, and the rule they enforce
#393's table, extended with the three rows whose absence hid this. Measured columns are from the --warm world at the recorded-call seam.
That last row is why #393's rule needs sharpening rather than repeating. "0 is a failure for every warm shape" was written when every shape in the table fast-attached; the path-spec attach is a warm shape whose 0 is correct today, because it pays a devpod up instead of asking. The rule that is total over all four arms, and is what #393 meant:
A launch that attaches without an up has made exactly one liveness observation. A launch that has made none must up.
So 0 status calls is a failure for any shape that skips the up, and correct for any shape that does not — and the two halves of the path-spec attach row can only move together, which is exactly the guard the follow-on ticket needs.
Pins that must exist, at rust/dl/tests/launch.rs's exact-call-sequence seam (the path spec is {ROOT}/cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo, whose leaf is the id launch_scenario.py gives the shim, so the shape is reachable with no new fixture):
dl <path> dotfiles in --warm: one status, no up, then context options and the refresh trip.
dl <path> -- true in --warm: no status, the identified up, both setup trips, the command trip. The before-picture of the next ticket, and the pin that makes it visible at all.
dl owner/repo@cold dotfiles: two status today, one after slice 3.
The sharpened rule as prose beside them, since it is the thing a future cache or a future substitution trips over.
Per #145's lesson, prove they can fail: flip run_dotfiles to placement.is_running() locally and pin 1 goes red on the spot. That is the test #393's table could not write.
Build slices, in dependency order
Slice 1 — the pins that were missing. No production change. Add rows 1-3 above plus the rule as a comment; all three are green on origin/main, and row 1's ability to fail is demonstrated by the local flip described above. CHANGELOG: none.
Slice 2 — split the arm. Depends on 1. Placement gains Creatable; place's Plan::Creatable branch builds it and prepare keeps building Creating; Creating's doc comment is rewritten to say what it now means (devpod was asked and denied this id); workspace_id()/source()/naming() gain the arm identically to Creating; is_running() becomes attachable() with the doc above; run_attach, run_up_verb and the unit test at flows/launch.rs:5359-5380 follow the compiler. No behaviour change and no call-count change — every pin from slice 1 stays green, which is the slice's whole acceptance test. CHANGELOG: none (internal).
Slice 3 — run_dotfiles reads the placement where the placement knows. Depends on 2. The exhaustive match above. Red first: dl <ws> dotfiles 2 → 1 and the cold-triple row 2 → 1, with dl <path> dotfiles staying at 1 and no up appearing anywhere. CHANGELOG: one devpod round trip off dl <ws> dotfiles and off a cold dotfiles.
Slices 2 and 3 could be one PR; they are separate because slice 2's acceptance criterion is nothing changed and slice 3's is two counts changed, and a reviewer reading one diff cannot check both.
Next ticket, and it is a decision rather than a slice
dl <path> and dl <git-url> pay a full up against an already-running workspace. The argv is in the second breadcrumb: zero status calls, a fully identified up, both setup trips. Arithmetic on #388's and #393's own figures — up-on-running 2.12-2.23s, an empty ssh trip 2.07-2.33s, status 0.569s — puts it at -5.7s to -6.4s on dl <path> -- cmd warm, which is the shape this map is named after and the largest number left on it outside the ssh-multiplexing work.
Slice 2 is what makes it a change to place alone: promote Creatable to Known by asking, and every consumer downstream already handles it. It reintroduces none of #393's four refusals — it is one observation per launch, an observation and not a memory, and it removes an always-up rather than adding one — and the precedent is already in the divergence table, row 21: the no-argument selector was routed through the same probe on exactly this argument, "the extra probe is the fast-attach contract every other entry already pays." The path-spec shape is the last entry not paying it.
What that ticket must settle and this one deliberately does not:
The derived id is a path leaf (path_leaf, divergence row 20), so ~/a/foo and ~/b/foo are one id. devpod status carries no source; devpod list --output json does, at 0.404s. Whether fast-attach needs a source check or merely inherits a hazard up --id foo <other-source> already has depends on what real devpod does with that command, which is a measurement nobody here has taken.
Whether the CreateRecord::NeverCompleted guard run_attach applies to Known is sufficient for a workspace this launch did not create.
Whether git-URL specs come along or stay behind.
A new divergence row, since Python always upped for a path spec.
Falsification conditions
#393's two stand unchanged. One is added, for the decision above:
Reopen the split if a third construction of either creatable arm appears, or if attachable() ever needs a caller that wants the negative answer — at which point the honest shape is a three-valued liveness() returning observed / undescribable / unobserved, and the two-projection compromise above stops paying.
Activity
blooop commentedon Aug 24, 2026
finding — the ticket stops at step 1's caveat: the three arms do not agree, and the arm
that disagrees makes the change a regression rather than a saving.
placement.is_running()andlifecycle::workspace_state(...).is_ok_and(ContainerState::is_running)give different answers for
Placement::Creating, and the difference is not in the arm'sown reading. It is in which of the two ways a
Creatingis built:Creatingwas builtis_running()workspace_statere-askplace_triple's cold path (prepare)falsePlan::Creatable(a path or a git source)falseRunning— disagreesPlan::Creatable's own doc comment is where the trap is written down:So for
dl <path> dotfilestheworkspace_statecall inrun_dotfilesis not a duplicate.It is the only liveness observation the command makes, and #393's invariant — one
observation of liveness per launch, an observation not a memory — is what it is satisfying.
Placement::Creatingcarries no memory of liveness to reuse, because nothing ever looked.Reproduced at the binary boundary
rust/dl/tests/launch.rs,--warmworld, spec = the path of the running workspace's ownclone so the derived leaf is the id devpod knows (
devlaunch-main-zovomobo):On
origin/main(57955a3), one status and noup:With the slice applied (
if !placement.is_running()):stderr gains
Starting workspace devlaunch-main-zovomobo...for a workspace that wasalready running.
Three things wrong with that, in the ticket's own terms
devpod statusgoes to 0 on a warm shape. The pre-up devpod status call costs 0.43-0.74s on every launch #393 named that row itself: 0 is afailure for every warm shape. The pin the ticket asks for, written honestly, fails on
the change it was written to make green.
up) reintroduced, on exactly the shape where the statuscall was load-bearing. Arithmetic with The pre-up devpod status call costs 0.43-0.74s on every launch #393's and Re-baseline launch latency on the Rust build #388's own figures: minus one status
(0.569s), plus one
up-on-running (2.12-2.23s), plus two ssh trips (2.07-2.33s each) —about +5.7s to +6.4s on this shape, and a
workspace_result.jsonrewrite that movesverdict_cache's anchor on everydl <path> dotfiles.upit starts making is a fully identified one —--init-env DEVLAUNCH_WORKSPACE_ID, launch lock, tools — whererun_dotfiles's doc comment saysthis verb's
uppasses no identity anddotfiles_starts_a_stopped_workspace_first_and_says_sopins
devpod up {MAIN} --ide none {PIXI}for it. That pin still passes, because it usesa named spec; the path-spec shape has no pin at all, which is why this was invisible.
The other two arms do agree
Known { state }— the state came from adevpod statusduring placement, so there-ask agrees modulo the ~0.6s window The pre-up devpod status call costs 0.43-0.74s on every launch #393 already accepts.
Listed—is_running()isfalse; the re-ask is a secondworkspace_stateagainstthe devpod whose first one just failed, so it fails again and reads
false. Agrees exceptunder a devpod that answers differently between two calls seconds apart, which is the same
window as above.
What this leaves for #393's slice
The duplicate is real for the named-spec shape (
dl <ws> dotfiles,dl owner/repo dotfiles),which is the shape #393 measured: there
Placement::Knowncarries the observation andrun_dotfilesasks again for nothing, and the existing goldendotfiles_refreshes_a_running_workspace_without_bringing_anything_uppins the two statuscalls with a comment explaining them. It is not real for the path-and-git-source shape,
where the
Creatingarm carries no observation because none was ever made.So the slice as written is not one substitution. It needs a decision this build stage should
not make alone, and the shapes are:
Placementwhen ithas one and keep the live ask when it does not — which is a match on the arm rather than
a call to
is_running(), sinceis_running()'sfalseis deliberately overloaded("nothing said it was running, which is not the same as saying it is not"). No new type,
no new arm; the honest reading is that
Creatingcannot answer this question and thecaller has to ask.
place,not to
run_dotfiles, and puts a status round trip on thedl <path>attach path thatdeliberately has none today.
if the win is only the named-spec shape, it is 0.57s off a verb nobody waits on in a
loop, against a call site whose overloaded
falseis easy to get wrong twice.The pin table also needs a row it does not have:
dl <path> dotfiles, warm = 1, andthat 1 is
run_dotfiles's own call. Any pin table without that row cannot catch this.Behaviour is unchanged and nothing is pushed: the probe test and the substitution were both
reverted, and the worktree is byte-identical to
origin/mainat 57955a3. Guards there:cargo fmt --checkandcargo clippy --locked --all-targets -- -D warningsclean;cargo test --workspacegreen apart from one pre-existing flake,aid'sa_pasted_multi_line_prompt_arrives_whole_rather_than_leaking, which fails only when thefull workspace run loads the machine and passes on its own and with its whole target
(
cargo test -p aid --test interactive, 7 passed). It is a real pty timing race onorigin/mainand belongs to nothing here.[-]dl <ws> dotfiles reads the state it was given, not devpod again[/-][+]dl <path> dotfiles has no liveness pin, and the status call there is not a duplicate[/+]blooop commentedon Aug 24, 2026
breadcrumb: retyped
wayfinder:buildtowayfinder:grillingby the manager, and the title changed with it. The build attempt above did the right thing by stopping: what looked like a slice was a question wearing a slice's clothes, and the answer decides whether there is any code here at all.The question this ticket now asks, from the finding above:
Known/Listedonly, keeping the ask forCreating), ask once earlier for the creatable shape, or drop the slice entirely. Note what the original slice's whole justification was: one duplicate removed, worth 0.57s. Any option that adds a match arm to save 0.57s owes principle 2 an argument it did not have to make when it was a deletion.dotfilesshape has no liveness pin, which is exactly why this was invisible. The pre-up devpod status call costs 0.43-0.74s on every launch #393's pin table needs that row.Map #139's entry for #393 has been corrected to record the falsification. The refusals #393 established stand unchanged; only its "one duplicate" removal does not.
blooop commentedon Aug 24, 2026
breadcrumb: the arm's doc comment is false for half of its values, and that is the defect rather than a consequence of it.
Placement::Creating(flows/launch.rs:2134) says, of itself:True of the value
placebuilds atlaunch.rs:2576fromPlan::Creatable. False of the valuepreparebuilds atlaunch.rs:2341, which is reachable only throughResolution::Cold, which is reachable only whenlifecycle::resolve_known_workspaceaskeddevpod statusfor the derived id — and, where a record named a different one, for that too — and devpod denied it (KnownWorkspace::Unknown,lifecycle.rs:653).So one sentence, one arm, both true and false depending on which of two lines built the value. #393 read the true half of it and wrote a decision on that reading. Nothing downstream can tell the halves apart, because the type does not spell the difference.
blooop commentedon Aug 24, 2026
breadcrumb: two more shapes measured at the seam the finding used —
rust/dl/tests/launch.rs,--warmworld, thedevpod_shim.pycall log. Probes reverted; the worktree is byte-identical toorigin/mainat 57955a3.dl blooop/devlaunch@cold dotfiles— a cold triple, so theCreatingthatpreparebuilt:Two status calls, both denials of the same id, the second bought and thrown away. So the finding's "agrees" row understates this arm: for a
Creatingthatpreparebuilt the re-ask is not merely consistent, it is redundant, and removable on its own evidence — devpod denied the id moments earlier, and the one case where that could have changed underneath is a sibling create, whichup_under_stagere-asks about anyway on the other side of its wait (launch.rs:1091-1104) for aNaming::Createrequest, which is what this verb passes for this arm.dl {ROOT}/cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo -- true— a path spec whose leaf is the id the shim knows, workspace Running:Zero status calls, and a fully identified
upagainst a container that is already running, plus both setup trips. That is refusal 3 — always-up— shipping today, unpinned, on the shape this map is named after. It is the same defect as the dotfiles one seen from the other side: the arm cannot say "running", so this shape never asks and always ups.blooop commentedon Aug 24, 2026
agent-decided (1):
The answer
Question 1: none of the three as written. The substitution is narrowed to where an observation exists — that much of shape one is right — but it is narrowed by splitting the arm that carries two meanings, not by matching around it at one call site.
run_dotfilesthen matches aPlacementwhose arms each mean one thing, and the special case disappears because it stops being special: an arm that never asked devpod anything is an arm whose reader has to ask.Question 2: yes, and it ships first. The pin rows are the guard whose absence hid this, they are green on
origin/mainas written, and they land before anything moves.Principle 1 decided it, over principle 2, on the evidence of this ticket: principle 3 supplies the shape, and the reason the shape is worth its arm is that the one-arm version has already cost a falsified decision and a reverted build.
Question 1: the options
Option A — narrow at the call site:
matchinrun_dotfiles, keep the ask forCreatingThe finding's own first shape. It is correct behaviour and it is three lines. What it leaves standing is the sentence in the first breadcrumb:
Placement::Creating's doc comment is true of one construction and false of the other, andis_running()'sfalsestill means two things with a comment to explain which. The next reader of either gets what #393 got. Principle 1 outranks principle 2, and the cost is not hypothetical — it is this issue.It also leaves the redundancy the second breadcrumb measured: a
Creatingthatpreparebuilt asks devpod a question devpod answered moments ago, twice on the colddotfilesshape, and Option A pays that forever because it cannot tell that arm from the other one.Option B — ask once, earlier, for the creatable shape
Not refused. Unblocked, and ticketed separately — see the last section. Two reasons it is not this ticket's answer:
dl <path> dotfilesshape makes exactly one status call today (measured: 1). Moving it fromrun_dotfilestoplacestill makes one, and puts 0.57s on every other path-spec verb that has none. Strictly worse.dl <path>launch path with an open question of its own (the derived id is a path leaf). Its upside is ten times this ticket's, which is exactly why it should not ride along inside it.Option C — drop the slice, ship the pin
Honest, and it was the safe answer before the arm was measured. It forgoes 0.57s on three shapes for nothing, and — worse — it leaves the type saying something false, which is the thing that actually cost this map a cycle.
Option D — split the arm. Winner.
Placement::Creatingbecomes two arms, because it is two states:prepare's value, reached only throughResolution::Cold. Never warm, and the denial is the reason rather than an assumption.Plan::Creatable's value. Not "not running": unobserved. devpod may well already have this id;upis idempotent and is what settles it today.Then
run_dotfilesmatches all four arms and theCreatablearm is the only one that asks — which is not a special case but the arm's meaning. And the arm thatpreparebuilt stops paying for its second denial.The type shape
workspace_id(),source()andnaming()treat the new arm exactly as they treatCreating(Naming::Create, source positional), so nothing about what devpod is handed changes.is_running()goes, and is replaced by the positive question it was the only honest answer to:The rename is the load-bearing half.
is_running()promised to answer both directions and could only answer one; the comment apologising for that ("Nothing said it was running, which is not the same as saying it is not") is the smell, and a predicate named for the question it actually answers does not need it.run_attachandrun_up_verbreadattachable()unchanged in meaning — for both, "no positive answer" and "not running" lead to the sameup.run_dotfilesis the one site where they do not lead to the same place, so it does not get a projection at all:What is still not added: no cache, no TTL, no
cached_at, noOption<ContainerState>, and noRemembered— #393's deliberate absence stands.Creatableis the opposite ofRemembered:Rememberedwould carry a liveness reading from the past, andCreatablecarries none at all and says so.ContainerStateis untouched, five arms,Unknown(String)kept.Placementispubbut is not indevlaunch_core's prelude (lib.rs:127exportsLaunchandLaunchVerbonly) and appears nowhere outsideflows/launch.rs— 14 sites, one file, all compiler-flagged. The public-API snapshot is unaffected.What it costs
Creating. Naming a distinction the program already makes is what principle 3 is for, and principle 1 is what says a type that states something false is worth an arm to fix.dl <path> dotfileskeeps its 0.57s, and correctly: that call is the command's only liveness observation.placealone.Removed round trips, all measured at the shim seam: one off
dl <ws> dotfiles, one offdl owner/repo@<cold> dotfiles, one off theListeddotfiles shape (by inspection — the secondstatusis against the devpod whose first one just failed). No shape gains one.Question 2: the pins, and the rule they enforce
#393's table, extended with the three rows whose absence hid this. Measured columns are from the
--warmworld at the recorded-call seam.devpod statustodaydl <ws>,dl <ws> -- cmddl <ws> up, runningdl owner/repo@<new>dl <ws> dotfiles, warmdl owner/repo@<cold> dotfiles(cold triple)dl <path> dotfiles, warmrun_dotfiles's owndl <path> -- cmd, warmupThat last row is why #393's rule needs sharpening rather than repeating. "0 is a failure for every warm shape" was written when every shape in the table fast-attached; the path-spec attach is a warm shape whose 0 is correct today, because it pays a
devpod upinstead of asking. The rule that is total over all four arms, and is what #393 meant:So 0 status calls is a failure for any shape that skips the
up, and correct for any shape that does not — and the two halves of the path-spec attach row can only move together, which is exactly the guard the follow-on ticket needs.Pins that must exist, at
rust/dl/tests/launch.rs's exact-call-sequence seam (the path spec is{ROOT}/cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo, whose leaf is the idlaunch_scenario.pygives the shim, so the shape is reachable with no new fixture):dl <path> dotfilesin--warm: onestatus, noup, thencontext optionsand the refresh trip.dl <path> -- truein--warm: nostatus, the identifiedup, both setup trips, the command trip. The before-picture of the next ticket, and the pin that makes it visible at all.dl owner/repo@cold dotfiles: twostatustoday, one after slice 3.Per #145's lesson, prove they can fail: flip
run_dotfilestoplacement.is_running()locally and pin 1 goes red on the spot. That is the test #393's table could not write.Build slices, in dependency order
Slice 1 — the pins that were missing. No production change. Add rows 1-3 above plus the rule as a comment; all three are green on
origin/main, and row 1's ability to fail is demonstrated by the local flip described above. CHANGELOG: none.Slice 2 — split the arm. Depends on 1.
PlacementgainsCreatable;place'sPlan::Creatablebranch builds it andpreparekeeps buildingCreating;Creating's doc comment is rewritten to say what it now means (devpod was asked and denied this id);workspace_id()/source()/naming()gain the arm identically toCreating;is_running()becomesattachable()with the doc above;run_attach,run_up_verband the unit test atflows/launch.rs:5359-5380follow the compiler. No behaviour change and no call-count change — every pin from slice 1 stays green, which is the slice's whole acceptance test. CHANGELOG: none (internal).Slice 3 —
run_dotfilesreads the placement where the placement knows. Depends on 2. The exhaustive match above. Red first:dl <ws> dotfiles2 → 1 and the cold-triple row 2 → 1, withdl <path> dotfilesstaying at 1 and noupappearing anywhere. CHANGELOG: one devpod round trip offdl <ws> dotfilesand off a colddotfiles.Slices 2 and 3 could be one PR; they are separate because slice 2's acceptance criterion is nothing changed and slice 3's is two counts changed, and a reviewer reading one diff cannot check both.
Next ticket, and it is a decision rather than a slice
dl <path>anddl <git-url>pay a fullupagainst an already-running workspace. The argv is in the second breadcrumb: zero status calls, a fully identifiedup, both setup trips. Arithmetic on #388's and #393's own figures —up-on-running 2.12-2.23s, an empty ssh trip 2.07-2.33s,status0.569s — puts it at -5.7s to -6.4s ondl <path> -- cmdwarm, which is the shape this map is named after and the largest number left on it outside the ssh-multiplexing work.Slice 2 is what makes it a change to
placealone: promoteCreatabletoKnownby asking, and every consumer downstream already handles it. It reintroduces none of #393's four refusals — it is one observation per launch, an observation and not a memory, and it removes an always-uprather than adding one — and the precedent is already in the divergence table, row 21: the no-argument selector was routed through the same probe on exactly this argument, "the extra probe is the fast-attach contract every other entry already pays." The path-spec shape is the last entry not paying it.What that ticket must settle and this one deliberately does not:
path_leaf, divergence row 20), so~/a/fooand~/b/fooare one id.devpod statuscarries no source;devpod list --output jsondoes, at 0.404s. Whether fast-attach needs a source check or merely inherits a hazardup --id foo <other-source>already has depends on what real devpod does with that command, which is a measurement nobody here has taken.CreateRecord::NeverCompletedguardrun_attachapplies toKnownis sufficient for a workspace this launch did not create.Falsification conditions
#393's two stand unchanged. One is added, for the decision above:
attachable()ever needs a caller that wants the negative answer — at which point the honest shape is a three-valuedliveness()returning observed / undescribable / unobserved, and the two-projection compromise above stops paying.