Do not start a container this supervisor does not own - #105
Merged
davidmckayv merged 2 commits intoAug 21, 2026
Conversation
beardthelion
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
August 21, 2026 20:04
`ensure` treats a 409 from `createContainer` as the other request creating the same computer, and goes on to start the container by name. That is right when the container already there is ours and wrong the rest of the time: a container left by a deployment under a different namespace, made by hand, or put there by somebody who guessed the name gets started and handed back as this Bot's computer, and the server then sends the deployment's computer token to whatever is listening inside it. The file's own rule says otherwise. Ownership is checked in `inspectOwned` so that stop, reset and inspect treat a name that is not ours as absent, and this was the one path that adopted it instead. Now a 409 is followed by the same ownership check, and a name held by something else is refused with its own error rather than a Docker outage, since the daemon is answering and the thing an operator has to look at is the container. Reproduced against a real daemon before and after: a container named `openbot-computer-<bot>` carrying somebody else's label was started by `ensure` and returned as the computer; it is now refused and left alone, and a container this supervisor does own is still started. The second half is the readiness wait, which could not fail. `waitUntilAnswering` fell out of its loop at the deadline and returned, so a computer that never came up was reported ready, which is the exact outcome the function exists to prevent. It throws now, the timeout is configurable because it decides when a slow start becomes an error, and 503 still tells the caller to wait and ask again. The tests drive a real Docker socket and skip where there is not one. A stub cannot show any of this: the behaviour under test is the daemon's 409, not ours.
beardthelion
force-pushed
the
fix/supervisor-foreign-container
branch
from
August 21, 2026 20:52
3cf8bc2 to
cda2c5a
Compare
`supervisor` is not one of the root workspaces, so a root `bun install` never installs `dockerode`, and `bun test` from the root walks this directory anyway. Importing the client at module scope, or `../src/docker` which holds one, therefore failed to resolve in CI and took the whole file down instead of skipping it: the suite reported an unhandled error rather than a test result. It passed locally because a `bun install` inside `supervisor/` had already put the package there, which is the case `tests/clean-checkout.test.ts` was written about. That guard did not catch this one: it filters the declared dependencies to the single name that broke before, so a new one is invisible to it. Both imports now happen inside the function that decides whether the tests can run at all, alongside the socket check that was already there. Three reasons to skip rather than fail, none of them a property of the code under test: no socket on this machine, no client installed, or a socket that will not answer. Verified both ways, because a test file that skips everywhere would have made CI green by removing the coverage. With the root install alone the three tests skip and nothing errors. With the package present and a reachable daemon they run and pass, and removing the ownership check from `ensure` still turns the foreign-container one red.
beardthelion
force-pushed
the
fix/supervisor-foreign-container
branch
from
August 21, 2026 21:11
196d4da to
28f7d74
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.
Closes #102.
ensuretreats a 409 fromcreateContaineras the other request creating the same computer and goes on to start the container by name. That is right when the container already there is ours and wrong otherwise: a container left by a deployment under a different namespace, or made by hand, gets started and handed back as this Bot's computer, and its address is whereCOMPUTER_TOKENgoes next.The file's own header says a name that lacks the label is treated as absent. This was the one path that adopted it instead.
What it does
inspectOwnedcheck the rest of the module uses. Ours, and the request carries on as before, which is what keepsensureidempotent under concurrent calls for one Bot. Not ours, and it is refused withNameHeldErrorand left alone, stopped.NameHeldErroris its own class, answered as 409 rather than 503. The daemon is fine, and an operator sent to look at Docker would find nothing; what has to happen is that somebody looks at the container holding the name.waitUntilAnsweringthrows instead of returning at its deadline, asComputerNotAnsweringError, answered as 503 because the caller's next move is still to wait and ask again. A wait that cannot fail is a sleep, and it was reporting every computer that never came up as ready.EnsureOptionsfield. It decides when a slow start becomes an error rather than when a log line appears, so a deployment pulling a large image on a cold host should be able to say so.Verification
Reproduced against a real daemon before the change: a container named
openbot-computer-<bot>carryingsomeone.else=truewas started byensureand returned as the computer withstatus: unknown. After: refused, anddocker inspectshows it stillcreated.supervisor/tests/docker.integration.test.tscovers three cases against a real socket:Both halves reverted with the tests live to confirm they fail.
The tests skip where the socket is not reachable, which is most CI. A stub cannot show any of this: the behaviour under test is the daemon's 409, not ours, and a fake would be asserting about itself.
bun test supervisor/testsruns 11 with 3 skipped where there is no socket, 3 passing where there is.bunx tsc --noEmitandbunx biome checkclean. Whole-tree failure set identical tomain.One interaction
ensurenow has a failure mode it did not have, so a deployment whose computer takes longer than 60s to answer gets a 503 where it previously got a "ready" it could not use. That is the intended direction, andreadyTimeoutMsis there for the deployment that needs longer.