Skip to content

Check the planning agent at startup and before planning requests (#164) - #193

Merged
mchwang merged 42 commits into
mainfrom
fix/164-planning-probe
Oct 21, 2026
Merged

mchwang merged 42 commits into
mainfrom
fix/164-planning-probe

Conversation

@mchwang

@mchwang mchwang commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #164

What changes

  • PlanningAgent.probe(vendor, signal) (runner/planning.ts) is the planning check. It runs one planning invocation in planning's own worker and in the same lane D profile a real request gets: read, list and search only, vendor-only egress, and a mounted schema. It runs on the review's code at the snapshot's head, with a real request's budget, because a process's first job may build the agent image. It asks for the schema's own answer, {"ok": true}, and passes only on exactly that (isPlanningProbeAnswer). So it covers:
    • sign-in: the credential, and Anthropic accepting it;
    • a schema-constrained answer;
    • the read-only planning profile starting and returning output.
  • PlanningCheck (runner/planning.ts) controls admission:
    • start() runs the check for the vendor chosen in Settings when the server starts. It runs nothing when no vendor is chosen.
    • admit(signal) waits at most 30 s in all for the check that covers the vendor chosen now. All waiting requests share that check, and a request that stops waiting leaves it running. A check still running after the wait gets a refusal: "Planning is unavailable: codeboost is still checking the planning agent."
    • A failed check refuses requests for a minute with "Planning is unavailable: codeboost's check of the planning agent failed; it checks again on the next request after a minute. The check said: ". The reason comes last and unchanged, because some reasons end with a command to copy. The next request after the minute checks again.
    • The first request after the vendor changes starts that vendor's check and cancels the check it replaces.
    • assertPassed() runs again inside the server's recorded action, through the description's validate(). So a vendor chosen during the issue read never runs unchecked. Because that action runs inside the Store's transaction, assertPassed starts no check there. It refuses, and the needed check starts on the next turn.
    • The wrapped provider.invoke checks once more when it is invoked. This is defence in depth: E3 invokes it in a microtask right after the recorded action, where Settings cannot change, but a later caller might. It rejects rather than throws, and E3 then settles the request as failed with the refusal.
    • A check that has not settled within a real request's budget plus a minute fails and is cancelled, so the recheck path applies to it.
    • A reason that holds until codeboost restarts gets no promise of a recheck, and says only a restart can change it. That covers an abandoned worker, cleanup codeboost could not confirm ("off until codeboost restarts"), a database file with other hard links, and a missing CLAUDE_CODE_OAUTH_TOKEN, because codeboost's environment is fixed while it runs. Codex is told to choose another agent, which the next planning request then checks. After close, the refusal says planning is stopping. It matches only codeboost's own text, never the vendor's output.
    • A request whose wait ends without a verdict is refused as such, never recorded as a failed GitHub read. An aborted request reads "stopped waiting"; a Store that cannot be read reads "could not check".
    • close() cancels any running check and waits up to 20 s, including for a timed-out check's probe still in the worker, before the worker closes.
  • productionPlanning (web/planning.ts) starts the check at setup. describe awaits admit before the issue is read or a planning request is recorded. The server already records a GuardRefusal as a 409 and starts no agent, so web/server.ts is unchanged.

Interpretation

  • "Read-only access" means the read-only planning profile. The check runs in exactly that profile. It does not ask the agent to read a file, because nothing can confirm the agent's account of what it read.
  • "A clear 'planning unavailable' state" is shown as the refusal of a planning request: a 409 whose message starts "Planning is unavailable:" and names the reason. Showing it in the review view before a click would need web/server.ts and web/public/*, which other work owns right now. That is follow-up Show the planning check's state in the review view #198, which also covers the web/server.ts limits listed below.
  • Limits, documented:
    • A passed check is kept until a check for another vendor replaces it. A worker abandoned later, cleanup that does not settle later (the check's own run included, when its answer still came back), or a token revoked later, shows as a failed request, not as "Planning is unavailable".
    • A run whose cleanup did not settle gets the recheck promise, because its storage is retried before the next run. If planning did go off, the recheck reports that with the restart wording.
    • A refusal recorded when a request stopped waiting, or when the Store could not be read during the wait, is replayed if the browser resends the same action. Keeping a Store error out of the record needs web/server.ts (Show the planning check's state in the review view #198).
    • A check that runs before the runner's setup has set the runner-owned repository fails on a task with runner commits ("which is not configured"). It keeps the recheck wording, because the recheck a minute later passes once the runner's setup has finished. Without a runner block it fails every time, as every planning request would.
    • A recheck joins the worker's single startup recovery, so a recovery that never ends fails each recheck until codeboost restarts.
    • A check deferred from the Store's transaction can start during shutdown, before planning closes. Close then waits for it, within its bound. Stopping it earlier needs web/server.ts (Show the planning check's state in the review view #198).
  • Startup. The check starts before the server listens. If startup fails after planning's setup, the CLI exits, and the next start's planning recovery removes what the check left. Another caller of startServer is left with the check running, because startServer does not close planning when a later startup step fails. Fixing that needs web/server.ts, so it is part of Show the planning check's state in the review view #198.

Tests

  • test/planning-production.test.ts, "the planning check (Probe the planning profile at runner startup #164)", 39 tests. Each uses a stand-in agent whose probe passes, fails, throws or never settles. They cover:
    • the start at setup;
    • one check for every request;
    • refusing with the reason before the issue read;
    • rechecking after the wait;
    • one shared check, and an abort that leaves it running;
    • the 30 s wait, and admitting once the check passes;
    • the defaults through productionPlanning, with nothing overridden: a 30 s wait, a check failing after the planning budget and a minute, and a 20 s close bound;
    • no vendor, a vendor change, and a change while a request waited, including the agent cleared after the wait ran out;
    • a vendor chosen during the issue read: refused in validate, checked on the next turn, refused as still checking while that check runs, then admitted;
    • a probe that throws;
    • cancelling a replaced check;
    • close cancelling the check and waiting for it, close's bounded wait, no check starting after close (one deferred before it included), nothing admitted after close even under a passed check, and a request waiting at close told at once that planning is stopping, whether its check's probe never stops or then passes;
    • one deadline bounding a request's whole wait across a vendor change;
    • a check that does not settle in time failing and being cancelled, staying failed if its probe answers late, then rechecked;
    • the restart wording for every restart-only reason, each built by the code that gives it (a missing sign-in, an abandoned worker, unconfirmed cleanup, a database with hard links), and none from vendor output;
    • a Store that cannot be read once a wait has begun;
    • the stopping refusal after close, and the Codex wording;
    • close waiting for a timed-out check's probe;
    • a request still waiting after its check was replaced and the vendor changed back, moving to the newer check at once;
    • the deferred check never throwing when the Store cannot be read;
    • many requests waiting on one check without Node's listener warning;
    • a multi-line reason kept whole at the end of the refusal;
    • the provider refusing to invoke an agent whose check has not passed;
    • through the server: a 409, no request recorded, no agent called.
  • test/planning-worker.test.ts: the check runs as a planning job in planning's worker, and the stub answers only the check's prompt and schema. It fails without a sign-in, and with Codex before any lock or Docker work. A wrong answer fails it, and only the exact answer passes.
  • test/agent-planning.test.ts (real Docker): the check against Anthropic with an invalid token fails with the vendor's 401 and leaves nothing behind.
  • Live probe (test/agent-planning.test.ts, "passes the planning check with a real sign-in (live Claude)", added to scripts/live-probes.ts and the Live probes workflow): the check's own prompt and schema, answered by the real Claude service.
  • test/browser/plans-production.spec.ts: the lane D stand-in gets a probe to fit the type. The spec chooses no vendor, so the check checks nothing there; it passes locally.

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 Heads reviewed Findings What changed
1–7 1a81c20/ce80552 → 02e32b9 blockers and majors: a check started inside the Store transaction, replaced checks left running, unbounded waits, the reason cut or reordered check started outside the transaction; replaced checks cancelled; one deadline per wait; reason last and whole; live probe added (d2b4a94)
8–20 d2b4a94 → de7c8f5 minors: recovery and shutdown wording, restart-only wording matched on vendor output, the answer check too loose, close not waiting for timed-out probes UNTIL_RESTART on codeboost's own text only; exact JSON answer; close waits for probes; follow-up #198 filed for server-side limits
21–28 ccf6bd1 → e0e46cb minors and nits: restart wording, a cleared agent mid-wait, test reasons not built by the code that gives them, Codex stand-ins that pass reasons built from their sources; Codex stand-ins fail as the real probe does; wrapped invoke's forwarding tested
29–35 4cc95fb → 0472de6 minors: defaults not pinned, admission after close (passed check, no vendor, waiting request), close not waking waiters fake-clock defaults test; close refuses first everywhere and wakes waiters; tests fail without each fix (checked by removing it)
36 base e46cb68 / head 6888587 none: CLEAN —

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

  • The abandoned worker's text ("The first planning request after the restart removes them") is kept: the startup check is a planning request through that worker.
  • A refusal quotes the check's reason as given, after "The check said:", including planningOutput's request wording: its sign-in and usage advice applies, and the one-turn probe cannot hit the turn limit.
  • checked() does not close planning after each test: close waits up to 20 s for never-settling probes, and the check's timers are unref'd.
  • A worker that never ends a job keeps one timed-out probe per recheck window for close to wait on; close's 20 s bound holds (commented).
  • Skipping the startup check when a runner is configured: the minute of "not configured" refusals is the accepted limit above.

Merges of main at e46cb68 (#196, #163's planning budget) resolved one table-row conflict in plan-library-alignment.md keeping both rows; the review rounds after it read the merged diff.

Clean independent-agent pass authorizing the Copilot request: round 36, base e46cb68, head 6888587.

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

Review-lesson audit

  • Fakes and stand-ins that did not answer as the real code does (Codex probe passing, made-up reasons, the browser stand-in's comment): covered by the existing rule on fakes ("A fake of an external tool … must give … each part of the answer the code reads") and the rule that a test's setup must leave the state the production path would.
  • Behavioural claims in comments, docs and the PR body that were false on one path (shutdown timing, "after close nothing is admitted", counts): covered by "Treat every behavioural claim … as something to verify".
  • Tests that could not fail (defaults not pinned, a listener limit Node never warns about): covered by "The Design system and plan format #1 Rule of E2E Tests" and the claim rule.
  • Evidence without labels (live-probe heads): covered by "In evidence records, label cited commits".
  • 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 22 commits October 20, 2026 18:16
PlanningAgent.probe runs one planning invocation in planning's worker and
profile, asking for the schema's own answer. PlanningCheck starts it when the
server starts and again when the vendor in Settings changes; productionPlanning
waits for it in `describe`, before the issue is read or anything is recorded. A
failed check refuses the request with "Planning is unavailable: <reason>" for
a minute, after which the next request checks again. Shutdown cancels a running
check before it closes the worker.

Closes #164

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1: the probe starts synchronously again (a deferred start broke
three tests and let close wait out its cap); the check gets a real request's
budget, since the first job may build the image, while a request waits for it
at most 30 s; the request's validate refuses a vendor chosen during the issue
read until its check passed; the refusal no longer advises a retry that cannot
help; tests cover close waiting, the close cap, the wait and a wrong answer.

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

Review round 2: assertPassed, which the server calls inside its recorded
action, refuses and starts a needed check on the next turn, so no worker,
ledger or owner-token write runs inside the transaction; a check another
vendor's replaces is cancelled; one deadline bounds a request's whole wait;
the stub answers only the check's own prompt and schema; tests for a probe
that throws and a replaced check.

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

Review round 3: tests show no check starts after close and one deadline bounds
a wait across a vendor change; the deferred check reads the vendor again; the
refusal ends its reason with a period and says a new agent is checked at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/implementation/plan-library-alignment.md
…heck (#164)

Review round 4: a reason ending with a command is no longer followed by text;
a request whose check was replaced while the vendor changed and changed back
waits for the current check; the provider checks again as it invokes; the
deadline test has margins a busy machine keeps.

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

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

Review round 6: a request waiting on a replaced check moves to the current one
without waiting for the old one to stop; the deferred check reads the vendor
inside its try; the refusal says 'Retry shortly' and names the configured
recheck window; wider timing margins; the vendor test checks the leftovers
record is gone.

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

Review round 7: the planning check against the real Claude service runs as a
live probe in the Live probes workflow; a request no longer stays attached to
the check after it settles; the still-checking refusal says the first check
can take minutes; the shutdown waits are documented; wider timing margins.

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

Review round 8: docs and comments say the planning check at startup is the
process's first planning job; waiting requests no longer trigger Node's
listener-leak warning; the stopping refusal carries the unavailable prefix.

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

Review round 9: the wrapped provider rejects instead of throwing; docs and
comments say the startup check recovers only when it can sign in, holds the
lock until the process ends, and builds the image beside the runner's;
assertPassed after close says planning is stopping; wider timing margins.

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

Review round 10.

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

Review round 11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 12: a check that has not settled within a real request's budget
and a minute fails and is cancelled, so the recheck applies; a reason that
holds until a restart gets no recheck promise; the invoke-time check is
described as defence in depth; startup failure exits only in the CLI; test
names and docs say what they show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st the cancel (#164)

Review round 13: an aborted request or Store error while waiting for the
check is refused as such, never recorded as a failed GitHub read; a missing
token, read only at startup, gets the restart wording; tests show a timed-out
check is cancelled and stays failed if its probe answers late; durations
under a minute read correctly.

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

Review round 14: a database with hard links gets the restart wording; the
restart match ignores vendor output; close also waits, within its bound, for
a timed-out check's probe; a Store failure while waiting reads as a failed
check, not a stopped wait; shutdown cancels with the shutdown stop; the
timeout rejects before it aborts; a test pins the probe's repository, head,
prompt and schema; wider recheck margin; 'recheck window' in test names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mits stated (#164)

Review round 15: cleanup that did not settle gets the restart wording; after
close the refusal says planning is stopping; Codex is told to choose another
agent; the Store-error test now fails the read after a wait has begun; the
docs and comments state the shared-recovery, kept-pass and shutdown-deferral
limits; test names say what they show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ort first (#164)

Review round 16: a run whose cleanup did not settle keeps the recheck promise
(its storage is retried, and the next check reports a real restart need), so
the wording no longer depends on whether Claude printed anything; a request
already aborted starts no check; docs state when a passed check is kept, the
check's own unsettled cleanup, the lock after an abandoned worker and the
replayed refusal.

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

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

Review round 18: the check passes only on {"ok": true} written once; the
cleanup-wording test uses cleanupFailed's real shape; test names say 'still
checking' and 'recheck window'; the docs list the Store-error replay limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang and others added 7 commits October 20, 2026 21:10
…164)

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

Review round 19 nits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n docs (#164)

Review round 20: assertPassed's refusal says the chosen agent changed and is
being checked; docs say a first job whose recovery fails leaves recovery to
the next, that shutdown cancels the check after the server's grace, and how a
check before the runner's setup fails on a task with runner commits (kept as
a recheck, since the runner's setup sets the repository later).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 21: refusals for restart-only reasons say codeboost must be
restarted after the cause is fixed; the still-checking refusal no longer claims
every slow check is the first; the doc says shutdown cancels a check once
planning requests settle or after the grace; the replaced flag, always implied
by a changed current check, is gone; a test uses AgentWorker's real text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 22: a restart-only refusal says only a restart helps and to fix
the cause first if the reason names one; the unreachable still-running branch
is gone; the check timeout's doc says no request waits that long.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a wait (#164)

Review round 23: the restart-wording tests build each reason with the code that
gives it (abandonedWorkerText is exported for that); a request whose wait ran
out goes on when Settings cleared the agent meanwhile; a Store error is labelled
a stopped wait only when it is the request's own abort; the null-to-Claude path
is the one tested for a vendor chosen during the issue read; the abort signal's
listener limit, which Node 26 never warns about, is no longer raised; the
suggestion-cancel row names the check that fails before the worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 24: the request's signal is checked at the top of each loop with
no await before the race, so the second check is gone; admit's doc covers no
vendor chosen; the refusal prefix's doc names its refusals as examples; the
recheck window's wording reads one way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang and others added 13 commits October 20, 2026 22:12
Review round 25: the tests that switch to Codex give it the probe answer
PlanningAgent.probe gives (CODEX_PLANNING_REFUSED) and expect its refusal; a
doc line is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 26: the vendor-output, unsettled-cleanup, Codex and multi-line
reasons are built by planningOutput, PLANNING_FEATURE.cleanupFailed,
CODEX_PLANNING_REFUSED and assertClear's wording; web/planning.ts says when the
check starts with no vendor chosen; a comment names the recheck window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fix/164-planning-probe

# Conflicts:
#	docs/implementation/plan-library-alignment.md
…164)

Review round 27: the sign-in refusals use planningOutput's own text and the
vendor-change test CODEX_PLANNING_REFUSED; checked() takes only the vendors
Settings accept; the suggestion-cancel row gives the runner-repository case as
one example of a check failing before recovery; comments rewrapped;
abandonedWorkerText moved below the module's re-export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 28: the invoke test first shows a passed check forwards the call
as it is; the refusal for a changed vendor says codeboost checks it next, which
holds even if the deferred check cannot start; 'records a planning request' in
the docs; the browser spec's stand-in says no vendor is chosen there; one
import from runner/planning.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 29: one fake-clock test through productionPlanning, with nothing
overridden, shows a request waits 30 s, a check fails after the planning budget
and a minute, and close waits 20 s; it fails if any of them changes. The
refusal comment notes the Store's 4,000-character record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 30: #check and assertPassed test for close before the covering
check, so a request, its start and the wrapped invoke are refused as stopping
once planning closed; a test shows it. The defaults test's close step names what
close waits for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 31: admit and assertPassed test for close before they read the
vendor; the after-close test covers no agent chosen too. The PR body records
the live probe run on the head with #163's budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 32: #verdict tests for close before a pass, so a check that
passes after close admits nothing; tests cover a check deferred before close,
start after close, and a request waiting at close whose check then passes.

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

Review round 33: a request waiting at close is refused as stopping at once,
even when its check's probe never stops; start's doc says it throws after close;
web/planning.ts says the check gates a request when a vendor is chosen; a test
covers validate meeting a check still running; a comment notes what a worker
that never ends a job leaves for close.

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

Review round 34.

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

Review round 35.

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

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 remain in the reviewed admission lifecycle, production wiring, or regression coverage; deferred server and UI work is explicitly tracked in #198.

No open issues 🎉

What changed in this PR

Adds a startup planning-agent check and gates planning requests on its result, addressing #164. Review-view status and server lifecycle follow-ups remain in #198.

Changes:

  • Probes the configured agent through planning’s worker and read-only profile.
  • Adds bounded admission waits, retries, cancellation, and shutdown handling.
  • Adds regression coverage and a live Claude probe.
File Description
web/​planning.ts Starts the check and guards planning requests.
test/​planning-worker.test.ts Tests probe inputs, credentials, and exact answers.
test/​planning-production.test.ts Tests admission, retries, races, and shutdown.
test/​fixtures/​question-worker-stub.ts Supplies controlled probe answers.
test/​browser/​plans-production.spec.ts Adds the probe method to the stand-in.
test/​agent-planning.test.ts Tests real authentication and cleanup.
scripts/​live-probes.ts Registers the planning live probe.
runner/​question-leftovers.ts Clarifies recovery timing in comments.
runner/​question-agent.ts Extracts shared abandoned-worker wording.
runner/​planning.ts Implements the probe and admission lifecycle.
docs/​implementation/​runner-lifecycle.md Documents recovery and shutdown timing.
docs/​implementation/​planning-acceptance.md Documents check behavior and limitations.
docs/​implementation/​plan-library-alignment.md Records #164 implementation and follow-up.
docs/​implementation/​ci.md Documents the fifth live probe.
docs/​implementation/​agent-isolation.md Adds planning-check validation evidence.
.github/​workflows/​live-probes.yml Includes the planning live-probe test file.

Review effort: Balanced

@mchwang
mchwang merged commit 79deeec into main Oct 21, 2026
4 checks passed
mchwang added a commit that referenced this pull request Oct 21, 2026
… 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>
mchwang added a commit that referenced this pull request Oct 21, 2026
…#189) (#191)

* Remove test objects made without a runner label however the test ends (#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>

* Give the test-run sweep its own budget and an afterAll pass (#189)

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>

* Remove containers that hold a swept network; record the stand-in first (#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>

* Remove only exact holders of a swept network; test the holder sweep (#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>

* Close the remaining label-less leaks the review found (#189)

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>

* Record label-less objects first in agent-network; remove networks by 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>

* Test removal by full network ID; say what the runner sweep removes (#189)

Review round 6.

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

* Remove swept containers by full ID; track the blocker before it is made (#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>

* Remove the foreign-runner storage in its test; clean every storage in 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>

* Read network names as JSON; name the seccomp probe; test --no-trunc (#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>

* Never throw on output the sweep cannot parse; foreign creates inside 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>

* Sweep the failed-allocation test by allocation; guard unreadable lines (#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>

* Budget the in-test sweeps; exact wording (#189)

Review round 12.

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

* Sweep both runners of the recovery test; keep the first failure (#189)

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>

* Check the traps are live in the holder test; report every cleanup failure (#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>

* Keep the first failure in the replaced-keeper test; exact sweep wording (#189)

Review round 15 nits.

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

* Check the partial-inspect answer; sweep the replaced keeper; budget margin (#189)

Review round 16.

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

* Budget the replaced-keeper sweep; say what the holder test's budget covers (#189)

Review round 17.

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

* One per-file sweep in afterAll; real docker for the sweep; timed-out 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>

* Track the race and legacy volumes; cleanup loops inside try with real docker (#189)

Review round 19.

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

* Sweep other runners' objects when the test finishes, timeouts included (#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>

* Real docker in every shim; foreign-name containers swept on finish; killed-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>

* Budget the supervisor finish hooks; name a signal kill; exact comments (#189)

Review round 22.

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

* Register late-landing agent-network objects by name; exact comments (#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>

* Track the holder test's objects for afterAll, removed by exact name (#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>

* Report removeByName failures; records keep every name; shim scoped (#189)

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>

* Test removeByName's exact-name guard and reports; records outside shims (#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>

* removeByName treats only Docker's own missing answers as missing; tests 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>

* Real-Docker guard check on a free network; real error text in the fake; budgets (#189)

Review round 28: the holder test's removeByName check uses a network with no
endpoints, so a broken guard would remove it; the fake's stderr is Docker
27.4's own; the supervisor's finish-time removals get 90 s for two 30 s calls;
agent-network makes its records directory on first use; comment fixes.

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

* Holder test checks removeByName's exact removal and silence on a gone name (#189)

Review round 29: one free object of each kind is removed by exact name and
asserted gone, and a second removal of the gone names reports nothing; the
ID-prefix check's comment says what the names are; the test's finally removes
by exact name; the unit file's header names what the daemon test covers.

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

* The stand-in shim acts only on an exec in a proxy (#189)

Review round 30: any other exec passes through, so the shim can never run a
stand-in with an empty name, which Docker would give a random, unrecorded one.

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

* Doc: a test may sweep its own runner owner with removeLabelledObjects (#189)

Review round 31.

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

* Say what the killed-call fake leaves out (#189)

Review round 32.

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

* Sweeps keep the Docker daemon set when they loaded (#189)

Review round 33: a test that times out may leave DOCKER_HOST changed
(agent-supervisor points it at a missing socket until its handle settles); every
call in docker-sweep.ts now restores DOCKER_HOST and DOCKER_CONTEXT as they were
at load, with a unit test that fails without it; the context fake says its path
is shortened.

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

* Comment and naming fixes (#189)

Review round 34: what the loaded Docker address means for each call, which
stdout a failed call leaves unread, who adds the test-run label; the records
directory variable is lower case, as it changes.

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

* The racer shim acts only on its own allocation's create (#189)

Review round 35: should a race test time out with the shim on PATH, other
tests' creates pass through; withoutRunnerLabel's doc says what a wrapper left
on PATH does to a later test.

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

* removeByName's reports name the stage and the command run (#189)

Review round 36: removeByName takes a when prefix, as removeLabelledObjects
does, and names the command it ran (docker rm, docker container inspect).

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

* Sweep reports name their stage; a gone holder is not a failure; loaded daemon tested (#189)

Review round 37: removeLabelledObjects names the stage of each failed call and
says nothing of a holder gone since the listing (Docker 27.4's No such
container for it), still reporting any other failure; a test loads a fresh copy
of the module with DOCKER_HOST and DOCKER_CONTEXT set and shows the sweep keeps
them; unit tests cover both holder inspect answers.

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

* Track the never-started unlabelled agent container by name; comment fixes (#189)

Review round 38: Docker 27.4 removes a network a created, never started
container still names, so that test's container is tracked for afterAll; the
docs say which holder reads stay quiet, that the sweep reads stderr for them,
and that a wrapper can stay on PATH for the rest of the file; the loaded-daemon
fake says what it answers.

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

* Label agent-network's unnamed helper containers for afterAll; track the rogue endpoint (#189)

Review round 39: a helper run killed between create and start is never removed
by --rm, and is no holder once its vendor network is gone, so the curl, DNS and
concurrent-network helpers carry this file's run ID and afterAll sweeps it
(13-minute hook for two sweeps); agent-container's rogue endpoint is tracked by
name; the holder fake says the removals' stdout is not read.

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

* Track the peer, stand-in proxy and foreign keeper by name; holder test asserts the quiet sweep on the daemon (#189)

Review round 40: three label-less or foreign containers made with a 60 s client
could land after their test's finally, and once their network is gone none is
a holder, so each is tracked by name for afterAll; the holder test checks the
daemon's own answer for a gone container and that the sweep reports no failure
for it; the holder fake says what it leaves out; the unit file unstubs env
after each test; agent-network removes its records before the sweeps.

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

* Track the rogue-volume container before its create (#189)

Review round 41: should the create land after its client is killed, afterAll
removes the container by name before the label-less volume it mounts.

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

* Read the holder test's sweep warnings before restoring the spy (#189)

Review round 42: mockRestore clears the recorded calls, so the check that the
sweep says nothing of a gone holder read an empty list and could not fail; the
calls are copied first, and the test also checks the spy saw the sweep's own
lines.

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

* Storage release after a pass fails the test again; mark the invented fake answer (#189)

Review round 43: in the two tests whose cleanup now warns instead of throwing,
a release that fails after the test's own checks passed fails the test, as on
main, while a failure above still stays the one reported; the second holder
fake case says its answer is invented.

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

* removeByName: tested on unreadable output, quiet on a network gone before its removal (#189)

Review round 44: the fake gives an inspect answer that is not JSON (reported,
nothing removed, never thrown) and a network gone between inspect and removal,
which Docker 27.4 answers 'network <ID> not found' and removeByName now treats as
gone; the keeper comment says afterAll also removes it by name; a comment line
is rewrapped.

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

* Restore two failures main's cleanups had; report an inspect answer with no ID and name (#189)

Review round 45: the foreign-name supervisor test fails again when, after its
checks pass, the container's removal fails; the unsettled-allocation test checks
after its sweep that nothing of the allocation is left, as main's listing did;
removeByName reports a parsed answer without an ID and a name, and its doc lists
what it stays quiet about; comment blocks rewrapped.

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

* Comment fixes: what main's check was; what removeByName reads (#189)

Review round 46.

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

* Test that the real docker is found once at load and that shims refuse without one (#189)

Review round 47: a unit test loads the fixture with docker on PATH, then puts
another first, and checks REAL_DOCKER keeps the first; loaded with no docker,
requireRealDocker refuses. A docker found through a relative PATH entry is
resolved to an absolute path. The never-started agent container's comment gives
the real reason it is tracked.

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

* Test the load-time docker as the default and a relative PATH entry; client limit; messages (#189)

Review round 48: the finds-docker test loads the fixture through a relative
PATH entry, then puts another docker first and calls both removals with no
docker given, checking only the docker found at load ran; the network listing
in the killed-create test gets its 60 s client limit back; the supervisor's
removal message handles a docker that could not start; the fake says what it
answers.

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

* Every Docker client call the branch adds or moves in a test has a time limit (#189)

Review round 49: synchronous calls cannot be stopped by a Vitest timer, so each
one the branch added, moved or rewrote (removals, inspects, the foreign creates
and the ack-script run) takes CLIENT_LIMIT, 60 s with SIGKILL, as main's docker()
helper did; the supervisor's removal message names a kill by signal.

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

* Track the ack-script container by name; time limits on the moved and attached calls (#189)

Review round 50: agent-supervisor's label-less ack-script container is tracked
by name before its create and removed by afterAll; runner-workspace's docker
helper, which now runs calls this branch moved out of a try, takes CLIENT_LIMIT;
the seccomp probe and agent-network's attached helper runs are killed with
SIGKILL at their limit, as an attached run forwards SIGTERM; the killed-create
test's listing reports its own failure.

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

* Name a failed foreign-keeper removal after a pass (#189)

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>

* Workflow limits for the longer teardown; full test of the loaded daemon; 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>

* Live probes limit counts agent-planning's probe and teardown; clearer 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>

* Sweep quiet on a network gone since its listing; full loaded-daemon test; 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>

* Holder test checks its gone-holder shim fired; header and workflow comments (#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>

* Rewrap two comment blocks (#189)

Review round 56.

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

* The loaded-daemon fake gives the volume its own holder (#189)

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>

* Track the supervisor's foreign-name containers for afterAll; stage in 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>

* Failure messages name the docker command; record checks first (#189)

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>

* Exact command in the listing's failure; holder test checks every sweep 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>

* Report sweep lines of the wrong shape; exact commands in removeByName'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>

* Gone checks need Docker's missing answer; sweep checks listed IDs and 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>

* agent-network's survival checks use objectExists (#189)

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>

* Every ID passed on as an argument has a checked shape (#189)

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>

* removeByName refuses a name that could read as an option; fake notes (#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>

* Test objectExists; gone cases reach their inspect; per-record errors 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>

* Gone checks in the touched tests use objectExists; fake and comment fixes (#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>

* Every gone check in a changed test uses objectExists (#189)

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>

* The proxy-start test's gone check uses objectExists; assert the raced 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>

* Shape checks before the gone checks; objectExists refuses an option-like 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>

* Trim the listing's stderr in its failure message (#189)

Review round 71.

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

* Pin every docker call's time limit; volume refusal reason; guarded record 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>

* Time-limit test runs all thirteen calls; holder search and no-space lines 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>

* Test the fixture's remaining guards; exact reasons in refusals and reports (#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>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

Probe the planning profile at runner startup

2 participants