Skip to content

Remove test objects made without a runner label however the test ends (#189) - #191

Merged
mchwang merged 79 commits into
mainfrom
fix/189-test-object-cleanup
Oct 21, 2026
Merged

mchwang merged 79 commits into
mainfrom
fix/189-test-object-cleanup

Conversation

@mchwang

@mchwang mchwang commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #189

What changes

Docker test objects without this run's runner label (io.codeboost.runner), or with another runner's, are now removed however their test ends: pass, fail, timeout, a create that lands late, or a client killed between create and start.

  • test/fixtures/docker-sweep.ts
    • removeLabelledObjects(label, value, when, docker = REAL_DOCKER) removes the containers, networks and volumes with label=value, and first every container that holds one of those networks or volumes, labelled or not.
      • Holders of a network are confirmed by network name, since Docker's network filter also matches the start of another network's ID.
      • Everything is removed by full ID.
      • It reports each failed call, naming the whole command, and each listed line it cannot read or that is not the shape it expects (a network with its full ID, a container ID, a volume name, a holder's full ID and its networks), with its stage. Such a line is skipped, so none is passed on as an option. It says nothing of a holder or network gone since the listing.
      • It reads the stdout of a call that exited by itself whatever its status (an inspect naming a gone container prints the others and exits 1), but not of a killed call, whose output may be cut short. Main read no output of a failed call.
      • It never throws. Eleven calls of 30 s each give a worst case of 5.5 minutes. Main's removeRunnerObjects made six calls (3 minutes), with no holder pass, ran the docker and environment on PATH at the time of the call, and named only a call's first two arguments; the hook budgets below rise for that.
    • removeRunnerObjects(owner, when?) is that sweep for one runner owner. The five Docker test files that called it on main still call it in afterAll; agent-planning and agent-question keep their own sweeps.
    • removeByName(kind, name, when, docker?) removes only an object of exactly that name, by its full ID, since Docker reads a missing name as an ID prefix. It stays quiet on Docker 27.4's own missing answers, including a network gone between its inspect and its removal. It reports any other failure, naming the exact command, and any answer it cannot read, including an ID that is not a full ID. It refuses a name that could read as an option. Every value either function passes on as an argument has a checked shape, so none can read as an option.
    • REAL_DOCKER is the docker found when the module loads, resolved to an absolute path. It is the default for both removals, so a shim left on PATH by a timed-out test is not called. requireRealDocker() refuses a shim when none was found.
    • Every call there runs with DOCKER_HOST and DOCKER_CONTEXT as they were at load. agent-supervisor points DOCKER_HOST at a missing socket until its handle settles.
    • objectExists(kind, ref) is for the tests' checks that an object is gone. It refuses a ref that could read as an option. Only Docker's own missing answer counts as gone, and any other failure (a killed call, a daemon that cannot be reached) throws, naming the command. Every gone check in a test this branch changes now requires Docker's own missing answer (objectExists, a helper's error matched against that answer, or an empty listing by allocation); on main some counted any failed inspect as gone. Checks in tests it does not change keep main's form.
    • CLIENT_LIMIT (60 s, then SIGKILL) is for the tests' synchronous Docker calls, which no Vitest timer can stop.
    • TEST_RUN_LABEL and TEST_FILE_LABEL are labels only tests write.
  • test/agent-container.test.ts
    • withoutRunnerLabel refuses a prefix that is not a create, since it puts its labels after create. It labels what it makes with a per-call test-run ID and this file's run ID. sweepWhenFinished removes the test-run ID in onTestFinished (6-minute budget), and afterAll sweeps the file's run ID.
    • Each object made under another runner's owner, under a random one, or with no labels is covered one of two ways.
      • Swept by allocation or owner: async tests register sweepWhenFinished at their start, and synchronous ones sweep in their finally.
      • Tracked by name before it is made. This covers named containers, networks and volumes, and the containers that mount a label-less volume.
    • afterAll removes the tracked names with removeByName, containers first. It then sweeps by file label and by owner, and reports every step's failure at the end. Its limit rises from 2 to 13 minutes, for the two sweeps.
    • The racer, foreign and unlabelled shims now act only on their own test's calls (swap already did). Every shim in the five files takes the real docker from requireRealDocker() rather than looking it up at test time.
    • Creates that may land late are made inside their try, so the finally removes what they made: the rogue volume (tracked by name before it is made), its container and the foreign keeper.
    • The seccomp startup probe's container is now named and tracked, and its call takes CLIENT_LIMIT. The rogue-volume test's finally removes the container before the volume it mounts.
    • "never removes a task keeper replaced by another runner" sweeps its allocation in its finally, so its budget rises from 60 s to 7 minutes.
    • "refuses task storage that belongs to another runner" releases its storage in its own finally, and a release failure after a pass fails the test there. On main it was pushed to taskFilesystems, so such a failure failed afterAll.
    • "rejects task volumes without their runner label" also checks the labels the wrapper puts on.
    • "never removes a network found by name after a killed create when its runner label differs" lists the network with CLIENT_LIMIT (SIGKILL; main's helper used SIGTERM), and a failed listing names its command. Its finally removal is replaced by an allocation sweep in onTestFinished.
    • "keeps a failed allocation whose cleanup did not settle live" checks, after a pass and after its sweep, that nothing of the allocation is left (networks included), in a 4-minute onTestFinished hook. Main's finally listed only containers and volumes.
    • A holder-sweep test checks against the daemon the Docker behaviours the sweep relies on, the sweep itself, and removeByName. Its budget is 8 minutes: the sweep's worst case and ordinary calls.
    • An it.fails test and its follow-up check that a keeper left by a test that stopped early is gone.
  • test/agent-network.test.ts
    • The shims that make foreign objects record each name, one per line, before they make the object, in a records directory outside the shims. afterAll reads it again.
      • The stand-in shim acts only on an exec into a codeboost-proxy-* container. On main it acted on every exec.
      • The three tests that read a record fail with "the shim recorded no foreign …" (or a name check) when it is empty. On main a missing record failed them with ENOENT.
      • "removes a network that lands … after its create client was killed" and "removes a proxy that was created even though its start failed" check the recorded name or ID before their gone check, so a shim that recorded something else is reported as itself; a leaked object is then left to afterAll's runner sweep. The proxy's removal takes CLIENT_LIMIT (none on main).
      • Their check that the foreign object survived moved into the finally and uses objectExists: only Docker's missing answer counts as gone, and any other failure is reported, naming the command, after the body's own checks.
    • Unnamed helper run --rm containers carry this file's run ID, and afterAll sweeps it. A helper whose client is killed between create and start is never removed by --rm, and is no holder once its network is gone.
    • The peer, the stand-in proxy and the partial-removal blocker are tracked by name, and the blocker is made inside its try. The afterAll hook's limit rises from 4 to 13 minutes.
    • The attached helper runs (curl, DNS, peer) are killed with SIGKILL at their limit: an attached docker run passes SIGTERM on to the container rather than exiting. A killed helper's container then runs on to its own short timer (15 s at most).
  • test/agent-supervisor.test.ts: the two foreign-name creates and the ack-script run are made inside their try, with CLIENT_LIMIT. The two foreign-name containers are removed by name when their test finishes (90 s hooks, for two calls of 30 s) and tracked by name for afterAll, as is the ack-script container. The afterAll limit rises from 3 to 7 minutes.
  • test/agent-gate.test.ts, test/runner-workspace.test.ts:
    • Their afterAll hooks, which already ran removeRunnerObjects, get budgets for the sweep's worst case: 4 to 6 minutes and 4 to 7 minutes.
    • runner-workspace's Sweep leftover path identity probe containers in startup recovery #181 test now sweeps its two owners in onTestFinished (6 minutes each), not in a finally, so the sweeps run on a timeout too.
    • runner-workspace's docker helper takes CLIENT_LIMIT.
    • runner-workspace's check that the probe's volume is gone now wants Docker's "no such volume" answer; on main any failed inspect passed it.
  • Failures kept from main: a cleanup that could fail a test on main still fails it, with Docker's own error, after the test's own checks pass. This uses a passed flag, or a check registered to run after the sweep. A failure in the test body stays the one reported.
  • test/docker-sweep.test.ts (15 tests, fake dockers that say beside them what they answer as Docker 27.4, and what they leave out or invent):
    • unreadable output;
    • a killed call;
    • removal by name;
    • the loaded daemon address, for every kind of call;
    • a holder, or a network, gone since the listing;
    • a holder line that is not an ID and networks;
    • listed lines that are not a container ID or a volume name, and a holder search line that is not a container ID;
    • every call's time limit and SIGKILL;
    • a docker that cannot start;
    • objectExists: gone only on Docker's missing answer, a throw naming the command on any other failure, at the loaded daemon;
    • the load-time docker as the default.
  • Workflows:
    • The Live probes job limit rises from 75 to 100 minutes. Its comment now counts all five probes, agent-planning's from Check the planning agent at startup and before planning requests (#164) #193 included (44 minutes), and the teardown limits, now 26 minutes: 90 minutes plus setup. test/vitest-config.test.ts pins the new value.
    • The Agent isolation comment says its 45 minutes cover ordinary runs, not every hook at its limit.

Accepted limits

  • In agent-container's recovery test, a cleanup AggregateError replaces the body's failure when both fail. This was already so on main.
  • Test budgets cover the sweep's worst case plus ordinary Docker call times, not every call at its own timeout.
  • Objects in synchronous test bodies are removed by their finally, which always runs.
  • An object that lands after its finish-time sweep has run, from a timed-out body or a killed process's Docker client, is not covered.
  • withoutRunnerLabel's wrapper, if a timed-out test leaves it on PATH, also unlabels a later test's matching create, which then fails. What it makes carries the file's run ID, and afterAll removes it.
  • Shims on main that make no foreign or label-less objects still act on every caller's calls. They leak nothing.
  • Docker calls this branch did not add, move or rewrite keep main's time limits.

Review record

All rounds were independent-agent reviews: a fresh general-purpose subagent with no author context, reading the full origin/main...HEAD diff each time, never only the fixes. Each round's fixes are one commit whose message starts "Review round N" and lists what changed.

Rounds Findings What changed
1–23 majors and minors: objects left by a test that stopped early, sweeps that missed holders or matched by ID prefix, a sweep run through a shim left on PATH test-run and file labels, finish-time sweeps, holder pass by network name, removal by full ID, REAL_DOCKER, removeByName
24–41 minors: objects that land after the finally, records that a late client overwrites, never-started containers that are no holder once their network is gone, DOCKER_HOST left changed tracking by name before the create, per-file records outside the shims, file label on helper runs, load-time daemon address, scoped shims
42–56 a major (an assertion read a spy after mockRestore cleared it) and minors: cleanups that lost main's failure, fakes not matching Docker 27.4, quiet answers that hid real failures passed flags and after-sweep checks, fakes copied from the daemon, exact missing answers, workflow limits (Live probes 75→100 min)
57–74 majors (time limits lost on moved calls) and minors: unlimited synchronous calls, gone checks that passed on a killed inspect, values passed on that could read as options, untested guards CLIENT_LIMIT, objectExists, shape checks on every value passed on, a test for each guard and each time limit
75 none: CLEAN —

Declined, with reasons (each repeated to later rounds so they were not re-reported):

  • withoutRunnerLabel's guard refusing a prefix that is not a create has no test: it is a precondition of a test-only helper inside a Docker suite file, and every caller passes a create.
  • The reports name the command as docker <args> even when a stand-in path is given: that argument is only for stand-ins that pass the call through or imitate it, so the command named is the one that ran.
  • Checks and Docker calls in tests this branch does not change keep main's form and limits.
  • The shims on main that make no foreign object (listed under accepted limits).

Merge of main at 79deeec (#193, #196): no conflicts; the review rounds after it read the merged diff.

Clean independent-agent pass authorizing the Copilot request: round 75, base 79deeec, head f6bf790.

Validated head f6bf790 (base 79deeec): CI test and real-docker passed on it.

Copilot review on head f6bf790 (review 5602245061): approval recommended, no open issues, no inline comments.

Review-lesson audit

  • Cleanup that missed an object however its test ended, and shims that acted on other tests' calls: covered by "A test's setup must leave the state the production path would" and by the claim rule (every claim of what cleanup covers was traced through timeouts and late creates).
  • Fakes that did not answer as Docker 27.4 does, or did not say what they leave out or invent: covered by the existing rule on fakes.
  • Checks that could not fail (a spy read after mockRestore, a guard with no test, a gone check that passed on a killed call): covered by "The Design system and plan format #1 Rule of E2E Tests" and "give it a test that fails if the claim is false".
  • Time limits lost or untested, and workflow sums that no longer held: covered by "Treat every behavioural claim … as something to verify" (for example "bounded by N seconds").
  • Cleanups that lost main's failure or its message: covered by the same claim rule and by the Design system and plan format #1 rule.
  • PR body statements that drifted from the diff: covered by "Treat every behavioural claim, in code comments, the PR body or docs".
  • Wording and layout nits: one-off; no rule.

No new rule is needed, so no AGENTS.md or CLAUDE.md change is in this branch.

🤖 Generated with Claude Code

mchwang and others added 30 commits October 20, 2026 17:52
…#189)

`withoutRunnerLabel` now labels each object it strips with a per-call test-run
ID and removes everything with that ID when the test finishes, passed, failed
or timed out. The owner sweep also removes containers that hold a swept
volume. In agent-network, the stand-in proxy and the blocker are removed in
`finally`. A pair of tests checks that a keeper left by a test that stopped
early is gone.

Closes #189

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: the onTestFinished removal gets 3 minutes, not the 10 s hook
default; afterAll sweeps every test-run ID again, for a create that lands
after a timed-out test's removal; the regression pair records the storage only
after its checks and takes it out of afterAll's list first; one ps call finds
every holder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#189)

Review round 2: a network made without the runner label stayed when a
runner-labelled proxy held it, so the sweep now removes the containers on a
swept network too (looked up by name, which also finds a created container).
The hook budget covers the sweep's ten calls, and the impostor's name is
recorded before the stand-in exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…189)

Review round 3: Docker's network filter also matches the start of another
network's ID, so holders are checked by network name; a real-Docker test
covers created and running holders and bystanders; hook budgets cover the
sweep; label-less creates in agent-container and agent-supervisor are tracked
before they are made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 4: the rogue volume is made inside its try and removed after its
container; the foreign network is listed again in finally; the holder test
records each object before it exists; the sweep reads a partial inspect; the
hook budgets cover eleven calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ID (#189)

Review round 5: the network and proxy shims record a name before they make
it, and the tests remove it in finally; the sweep removes networks by full
ID, reads output only from a call that exited by itself, and the holder test
runs its inspect with a missing ID, as the sweep meets it; the supervisor's
finally removals never throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Review round 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de (#189)

Review round 7: Docker resolves a container name before an ID prefix, so the
sweep lists containers and holders with --no-trunc, and the holder test keeps
a container named like a holder's short ID; the blocker is tracked before its
run; comment wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… afterAll (#189)

Review round 8: the storage made under another runner is removed in finally;
afterAll removes every storage even when one fails; the killed-create network
is listed by full ID; afterAll budgets have a minute over the sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…189)

Review round 9: a network name may hold spaces, so the sweep reads names and
holders' networks as JSON; the label-less seccomp probe is named and tracked
before it runs; the holder test keeps a container named like a swept
container's short ID; budget comments state the real margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…try (#189)

Review round 10: a line that is not JSON is reported and skipped; the
foreign-runner network and storage are made inside their try and removed by
allocation; the recovery-handle test removes by full ID through the sweep;
doc wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leanup

# Conflicts:
#	test/runner-workspace.test.ts
#189)

Review round 11: the foreign-runner failed-allocation test removes its
objects through the sweep, by full ID and never throwing; network lines that
are not a network are skipped, with a unit test; in-test sweeps have budgets
for their worst case and their own warning prefix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 13: the recovery test sweeps its two random runner owners after
its undo steps, so what a step could not remove or never recorded goes too;
the foreign-storage test's cleanup no longer replaces the body's failure;
doc wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lure (#189)

Review round 14: the holder test first asserts Docker's prefix match and its
name-before-prefix resolution, so its bystander checks test something;
afterAll collects every cleanup step's failure; comment states which ending
the it.fails pair tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng (#189)

Review round 15 nits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…argin (#189)

Review round 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…overs (#189)

Review round 17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leftovers (#189)

Review round 18: objects made without the runner label also carry this file's
run ID, so afterAll removes them in one sweep within its budget; the sweep
runs the real docker found at load, never a shim left on PATH; agent-network's
label-less objects and the rogue volume are also removed in afterAll should
their test time out; the volume test checks the wrapper's labels; cleanup
warnings are logged; the fake's note names its answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… docker (#189)

Review round 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#189)

Review round 20: async tests that make objects under another runner's owner or
their own random one register their sweep with onTestFinished, which runs
after a timeout too, so test budgets return to their old values; the shims and
afterAll use the real docker with a timeout; comment wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…illed-call test (#189)

Review round 21: every shim calls the real docker found at load and refuses
when there is none; agent-supervisor's two foreign-name containers are removed
by name when their test finishes, timeouts included; a unit test shows a
killed call's output is not read; one more race volume tracked; comments name
every removal afterAll makes and what the finish-time sweep cannot cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#189)

Review round 22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…189)

Review round 23: the three agent-network tests whose foreign object may land
after their own removal register it by name for afterAll, since the record
goes with the shim; the late-network test says why its network is swept;
comment wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…189)

Review round 24: the holder test's unlabelled objects and the swapped keeper
are tracked for afterAll; afterAll removes tracked names only when an object
of exactly that name exists, by its full ID, since some are named like ID
prefixes; withoutRunnerLabel uses sweepWhenFinished; the finish-time sweep
states what lands too late for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

Review round 25: removeByName reads JSON (names with spaces), reports a failed
call other than no such object; agent-network's shims append one name per line
and every name is removed; the foreign-label shim rewrites only its own
allocation's create; the holder test checks the prefix-named network goes; the
killed-call unit test first checks its fake and asserts the warning; comments
name the networks set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ms (#189)

Review round 26: the holder test checks removeByName leaves an object whose ID
starts with a now-missing tracked name; a unit test covers its no-such answer,
an ID-prefix match, a refused removal and a killed inspect; only a container's
name loses its leading slash; agent-network keeps its records outside the shims
so afterAll reads names a late client adds; sweepWhenFinished's doc covers
withoutRunnerLabel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts every kind (#189)

Review round 27: a failure before the daemon (an unknown context) is reported;
the fake covers containers, networks and volumes, found, missing, under another
name and refused; the unlabelled-create shim acts only on its own container;
agent-supervisor's finish-time removals use removeByName; doc and import fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang and others added 24 commits October 21, 2026 02:21
Review round 51: as on main, a docker rm of the foreign keeper that fails after
the test's checks pass fails the test with Docker's own error, before the
storage release's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on; keeper warning (#189)

Review round 52: the Live probes job limit rises to 90 minutes, as the teardown
hooks it covers now allow 24 (vitest-config pins it); the Agent isolation
comment says its 45 minutes cover ordinary runs; the loaded-daemon test answers
one network with a holder and one named container, so every kind of call is
checked; a failed foreign-keeper removal is warned of when the body failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… missing-record failures (#189)

Review round 53: the comment counted four probes, before #193 added
agent-planning's; with its 11-minute probe and 2-minute teardown the limits
total 90 minutes plus setup, so the job limit rises to 100 (pinned in
vitest-config); the refused-network and refused-proxy tests say so when their
shim recorded nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est; hook budget (#189)

Review round 54: the sweep treats Docker 27.4's 'network <ID> not found' on its
network removal as gone, as removeByName does, with a test that still reports
any other failure; the loaded-daemon test now lists a container, a network and
a volume, each with a holder, and checks every command line; the after-sweep
check's budget covers its three listings; the Agent isolation comment names the
shared sweep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mments (#189)

Review round 55: the holder test's shim leaves a marker, and the test checks it
before trusting the quiet sweep; the unit file's header says the sweep reads a
network removal's stderr too; the Agent isolation comment names everything its
45 minutes cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 56.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 57: the stateless fake listed the network's holder again for the
volume, after the sweep had removed it, which Docker would not; the volume now
has its own holder, and the test checks its removal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… unreadable-line reports (#189)

Review round 58: the two foreign-name containers are tracked by name for
afterAll too, should a create land after their finish-time removal; the sweep's
report of a line that is not JSON names its stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 59: the killed-create listing and the supervisor's after-pass
removal name the command that failed, as main's helper did; the refused-network
and refused-proxy tests check their shim's record before the error, so a shim
that no longer matches is reported as itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p line (#189)

Review round 60: the killed-create listing's failure message is built from the
arguments it ran; the holder test requires the gone-holder inspect's exit 1, and
accepts only the sweep's own removal lines, including the holders line, so any
failure or unreadable answer fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s reports (#189)

Review round 61: the sweep reports a network listing line that is not a network
and a holder line that is not an ID and networks, as removeByName reports an
answer that is not an ID and a name, with tests; removeByName's failure reports
name the exact command and ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… names; full commands (#189)

Review round 62: objectExists counts only Docker 27.4's missing answer as gone
and throws on any other failure, so a killed inspect no longer passes a removal
check (the export, follow-up and holder tests; runner-workspace's volume check
wants 'no such volume'); the sweep names whole commands in its reports, reports
and skips listed lines that are not a container ID or a volume name, and reports
a holder line with no space once; agent-network's records removal cannot stop
the sweeps; the keeper test's finally sweep says finally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 63: a killed or failed inspect in the finally no longer reads as
the foreign object being removed; Docker's missing answer counts as gone, and
any other failure is rethrown, naming the command, after the body's own
checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 64: network IDs from the listing, holder IDs and removeByName's
inspected IDs must be full IDs, or are reported and skipped, so none reads as
an option; an inspect line with no space is reported instead of parsed twice;
fake cases for each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…189)

Review round 65: removeByName checks the name it passes on (a volume's against
Docker's allowed shape, others for a leading dash) and reports a refusal, with
fake cases; the volume-name shape needs two characters, as Docker 27.4 does;
agent-network's two rewritten shims say which answers are Docker's and which
are invented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in afterAll (#189)

Review round 66: a unit test loads the fixture with a stand-in docker and
checks objectExists is true, false only on each kind's missing answer, and
throws naming the command on a killed call or an unknown context, at the
loaded daemon; removeByName's gone cases assert their inspect ran; the holder
fake's missing answer names the full ID; agent-network's afterAll reports a
record it cannot read and goes on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixes (#189)

Review round 67: the keeper-replaced, foreign-name and unlabelled-agent checks
use objectExists, and the holder test calls it directly; the PR says checks in
untouched tests keep main's form; the objectExists fake prints the unknown
context's quotes as Docker does and says its path is shortened; the
supervisor's tracked containers are described as lacking the runner label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 68: the by-ID removal and start tests, the rogue-endpoint test,
the recovery test's exists helper and agent-network's late-landing check use
objectExists; removeByName's refusal says the name could read as an option;
the objectExists fake marks its stdout as invented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… removal ran (#189)

Review round 69: the last gone check in a changed test uses objectExists; the
gone-network sweep test checks its removal ran before trusting the silence; the
stand-in shim's note says where stderr goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ike ref (#189)

Review round 70: the late-landing network and proxy-start tests check the value
their shim recorded before inspecting it, as the record tests do, so a shim that
recorded something else is reported as itself; objectExists refuses an empty
or dash-led ref, naming it; the proxy-start test's removal takes CLIENT_LIMIT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 71.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cord reads (#189)

Review round 72: a unit test loads the fixture with spawnSync observed and
checks every removal call is given 30 s and SIGKILL, and objectExists 60 s and
SIGKILL, so the budgets built on them cannot lose their limit unseen;
removeByName says a refused volume name is not one Docker allows; agent-network's
finally blocks report a record they cannot read instead of throwing over the
body's failure; the PR says what every changed gone check requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ines tested (#189)

Review round 73: the time-limit test answers so both removals make every call
(13), and checks each; a holder search line that is not a container ID is
reported and skipped, with a test; removeByName reports an inspect line with
no space as no ID and name; the holder read's no-space line is tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ports (#189)

Review round 74: tests cover a docker that cannot start (both functions report
and never throw), Docker's missing text with another exit status in
objectExists, an empty name in removeByName, and a holder line that is not JSON,
reported once; refusals of an empty value say so; an inspected ID that is not a
full ID is reported as that; the two fakes that answer removals say their
stdout is not read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang
mchwang marked this pull request as ready for review October 21, 2026 15:35
@mchwang
mchwang requested a balanced review from Copilot October 21, 2026 15:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No blocking issues were identified in the cleanup logic, regression coverage, or workflow changes.

No open issues 🎉

What changed in this PR

Addresses #189 by strengthening Docker test cleanup for objects missing this run’s runner label, including failure and timeout paths.

Changes:

  • Adds shared label-based and exact-name cleanup with bounded Docker calls.
  • Tracks test-created resources and adds cleanup regressions.
  • Adjusts teardown budgets and the live-probe job timeout.
File Description
test/​vitest-config.test.ts Pins the updated live-probe timeout.
test/​runner-workspace.test.ts Adds finish-time sweeps and stricter removal checks.
test/​fixtures/​docker-sweep.ts Adds holder cleanup, exact-name removal, and bounded calls.
test/​docker-sweep.test.ts Tests cleanup safeguards and Docker failure handling.
test/​agent-supervisor.test.ts Tracks foreign containers and bounds creation calls.
test/​agent-network.test.ts Records foreign objects and labels helper containers for cleanup.
test/​agent-gate.test.ts Increases the sweep’s teardown budget.
test/​agent-container.test.ts Adds resource tracking, finish-time sweeps, and leak regressions.
.github/​workflows/​live-probes.yml Raises the job timeout to 100 minutes.
.github/​workflows/​agent-isolation.yml Clarifies existing timeout coverage in comments.

Review effort: Balanced

@mchwang
mchwang merged commit 0ffd195 into main Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove test objects made without a runner label when the test fails early

2 participants