Repository navigation
Probe task storage path identity at runner startup (#162) - #180
Merged
Merged
Conversation
Runner startup now probes case sensitivity and Unicode normalization on the filesystem that holds the task checkouts and refuses to start when the configured pathIdentity disagrees. The demo uses the same shared probe.
The agent edits, and the runner inspects, a tmpfs volume in the Docker daemon's Linux kernel. The host attempts folder holds only the staging clone. On a macOS host the earlier probe reported APFS's folding and refused the setting that is correct for task storage. Startup now runs the probe with the built image, in a hardened --rm container on an anonymous volume made with task storage's own tmpfs options, and refuses on a mismatch. The host probe stays for the demo. The staging clone's own case collapse is #179. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
Independent review of #180: the runner's host checkouts (the staging clone and the rebase worktree) are not task storage, so the comment and docs now say only what the probe covers, and point at #179. The probe script takes its directory, so a test runs it on a host folder and compares it with the host probe: a script that stopped measuring fails on a host that folds names. A test comment no longer claims an order the test does not check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 2: --rm removes the probe only once its container has started. A client killed between create and start (its deadline, on a slow daemon) left an unnamed container and its volume that nothing would ever remove. The probe now names its container and, on any failure, removes it and its volume by that name, asking through the settle window when the client did not finish. If that does not settle, the error names the container to remove. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 3: `docker rm --force` exits 0 for a name that does not exist (Docker 27), so the first removal ended the settle window and a create that landed after the killed client stayed behind. Removal is now confirmed by `container inspect` reporting the name absent after the window. A docker that never started sent nothing, so it is neither removed nor waited for. The test's fake docker now answers rm and inspect as Docker does, and can land a late create. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 4. The script is now run on a folder that already holds CASE and the NFD name, which must read as folded on any host, so a script that printed a constant fails on Linux CI too. The cleanup's retry while Docker refuses a removal, its give-up with the name to remove, and a client refused before it started each have a test; a give-up after Docker still reports the container says so. The comment no longer claims every failed probe is removed: a create that lands after the window, or a runner that dies first, can leave one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 5: any Linux volume answers `true none`, so only the probe's arguments show it measures a volume made like task storage, in a container hardened like the keeper. A fake-docker test now checks the exact run arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 6: older Docker CLIs exit non-zero for `rm --force` on a missing name, and the cleanup then never asked inspect, so it waited out its whole window and said a container may remain when none did. Every removal is now followed by an inspect, as allocation's own removal does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues remain, and targeted regressions cover startup validation, probe hardening, and failure cleanup.
No open issues 🎉
What changed in this PR
Validates configured path identity against Docker task storage at runner startup, preventing incorrect plan path keys without silently changing configuration.
Changes:
- Adds a hardened filesystem probe with bounded failure cleanup.
- Rejects mismatched settings before admitting work.
- Shares the host probe with the demo and adds regression coverage and documentation.
| File | Description |
|---|---|
test/runner-workspace.test.ts |
Verifies real-Docker identity and cleanup. |
test/runner-production.test.ts |
Tests startup mismatch and probe failures. |
test/path-identity.test.ts |
Tests host probing, comparison, and measurement scripts. |
test/path-identity-probe.test.ts |
Covers Docker arguments and failure cleanup. |
test/cli.test.ts |
Checks concise startup refusal messages. |
scripts/demo.ts |
Reuses the shared host probe. |
runner/production.ts |
Validates identity before workspace assembly. |
runner/path-identity.ts |
Adds host probing and identity comparison helpers. |
docs/implementation/read-only-review.md |
Documents validation and host-filesystem limitations. |
docs/implementation/plan-library-alignment.md |
Records startup probing as implemented. |
agents/container/storage.ts |
Adds the task-storage probe and shares volume options. |
Review effort: Balanced
Review lesson from #180: a fake `docker` answered `rm --force` on a missing name with exit 1, while Docker 27 exits 0, so a cleanup that trusted that exit code passed its tests while it would leak against the real daemon. Added to the mirrored "Review readiness" section of AGENTS.md and CLAUDE.md in the same commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe fake names the Docker version its answers were checked against and gives the older CLI's rm answer for a missing name; the CLI test's fake says it models only a successful startup's calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No repository document defines a supported Docker, git or gh range, so "supported versions" left a fake's completeness undecidable. Changed in both mirrored files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe fake's inspect of a missing name now prints [] on stdout and its refused run uses Docker's own wording; the CLI test's fake names the version its success-path answers were checked against. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 13: a refused run's stderr has a second line on Docker 27.4.0, now in the fake and asserted in full; the CLI test's fake lists the network listing and build among its calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 14. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 15. Each cleanup call now gets the time left before giving up (at least 1 s), so a daemon that stops answering cannot stretch the bound, and the give-up reports the last answer Docker gave. A real-Docker test shows a never-started container made with the probe's exact mount loses its volume to the cleanup's own rm. The runner lifecycle doc and README name the startup check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 16. After the first ask, the probe cleanup checks the give-up time before each call, so a daemon that stops answering partway adds at most one call's floor and stop grace. On give-up it reports the latest outcome of either call (an unanswered rm included), or that Docker still reported the container. The fake no longer has states Docker never answers: a removal in progress applies only to a container that exists, and a hanging daemon starts at a set time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fused Independent review of #180, round 17. One helper now makes every cleanup call and checks the give-up time before each, and the give-up says only that Docker did not confirm the container is gone, with Docker's last outcome as the cause. A client-side exit 125 (a lost connection after the create was sent) no longer skips the settle window: only a refusal from the daemon itself does. The fake notes which rm and inspect output the code reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…text Independent review of #180, round 18: when giving up falls due during the rm, the last answer is the rm's refusal, not the inspect, so the test accepts both. The unreachable-daemon fake prints what Docker 27.4.0 prints. The cleanup comment names the 10 s cap on each call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 19: a probe that exits 0 with output other than one identity line is refused, now under test. The unreachable fake answers rm and inspect as Docker 27.4.0 does without a daemon, and the old-CLI note cites the 20.10.0 release notes it rests on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 20: the argv test now checks the -c argument is the measuring script itself, so a constant printed in its place fails; the plan-library alignment row cites the probe's code and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 21: every fake answered tmpfs's true none, so a probe that ignored the container's answer passed; the fake can now answer any identity and each field is checked on its own. A hung-daemon test shows the first cleanup call is cut at 10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 22: a slow daemon with almost no time left is still heard; output with a line before the identity is refused; the script's directory listing is checked on any host; and the tests that need a create to land assert that it did, with a 2 s client deadline to keep that margin under load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 23: the late-create fake landed before the first rm, so the test never exercised a create that lands after the cleanup has found nothing. It now lands after the first inspect, and the test checks that the first rm found nothing and a later one removed it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aves unchecked Independent review of #180, round 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 25: an absent answer to an inspect asked inside the window, arriving after it while a create lands, was taken as settled and left the container. The window now uses the time the inspect was asked; a fake with a slow, mid-landing inspect tests it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 27: the default-bound test accepted up to 50 s, so a longer settle window or give-up passed; it now allows 3 s over the 40 s it states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 28: timing tests caught only some changes to the 1 s floor and 10 s cap. The deadline is now a small exported function with exact values tested in both directions; the timing tests still show the cleanup uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a doc Independent review of #180, round 29: changes under about 3 s to the default give-up or the settle window passed the timing test. Both are now named exports with their exact values tested; the timing test shows they are the ones in effect. The removeProbeContainer doc sits above its function again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 32. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 33: both tests compared every volume on the daemon, so another run on it could fail them. They now count only volumes with the probe's tmpfs options, or the container's own volume by name. The per-call deadline comment states the tolerance the timing tests give. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 34. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 36. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 37: as written, the rule required a fake to reproduce output the code never reads (a removed container's name, full inspect JSON, build logs). Changed in both mirrored files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 37: giving up counted from the start of docker run, or a 15 s window, passed every test. The give-up test now needs the client deadline, the window and the cleanup time; the late-create test bounds the window from above. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 42: with CASE and the NFD name seeded together, a script whose case check read the NFD name (or the reverse) passed everywhere; one seeded name per field catches it on Linux CI. The busy test now shows both refusals were answered before a later rm removed the container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review of #180, round 43: a 3.7 s per-call floor at the call site passed under the 4 s ceiling; the ceiling is now 2.5 s and the stated floor tolerance roughly 0.4-2.5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #162.
Carries forward #167, an outside contribution from eastagiletracker. Their commit (
234486d) is kept with its original author. Later commits change which filesystem gets probed and how the probe cleans up after itself.What changed
ReviewServicederives plan path keys from the configuredpathIdentity. Nothing checked that setting against the filesystem it describes.The setting is kept and checked, not derived from the probe, so a review never silently changes its plan keys. This choice is recorded in
docs/implementation/read-only-review.md.agents/container/storage.ts), not in a folder underrunner.root.<runner.root>/<owner>/attempts, which holds only the staging clone. That clone is copied into task storage.caseSensitive true, none) and asked for APFS'sfalse, NFC.probeTaskStoragePathIdentity(imageId)runsdocker run --rmunder a unique name (codeboost-path-identity-<uuid>), using the built image./workis an anonymous local volume made with task storage's own tmpfs options.TASK_VOLUME_OPTIONSis now shared with allocation.--rmdoes not remove that, so a failed probe removes its container and volume by name.container inspectreporting the name absent confirms removal. It runs after everyrm, becauserm --force's exit code for a missing name differs between Docker versions (27 exits 0).--rmandrm --volumesremove it, but Docker only logs a failure to. The probe carries no owner labels, so recovery does not find such a container. A recovery sweep scoped to the database's runner owner token is Sweep leftover path identity probe containers in startup recovery #181. The code comment says this, and the maintainer accepted merging with this gap (see the audit).setUpRunner, right after the image build and before the workspace is assembled or anything is admitted. A mismatch, or a probe that fails, stops startup. The CLI prints the message and exits 1, with no stack trace.runner/path-identity.tskeeps Probe checkout path identity at runner startup #167's host probe for the demo, which reviews a host checkout. It now usescore/path-identity.ts's existingPathIdentitytype and explicit\uescapes.The runner's host checkouts can still merge paths on a host that folds names. These are the staging clone and the rebase worktree (whose starting head
#assertCheckoutalready guards). That is #179, which includes a reproduction.Validation
Labels: baseline
7cd81a8(main). Validated headbd65ff2: the local gates below. Its GitHubtestandreal-dockerchecks are listed under the PR's checks and are the full-suite and real-Docker gates. Intermediate checkpoints: every other commit named here.Local gates on the validated head (macOS host, Docker 27.4.0):
npm run typecheck: clean.test/path-identity-probe.test.ts(fake Docker): 26 passed.test/path-identity.test.ts: 7 passed, 1 skipped (the Linux-only host assertion).test/cli.test.tsandtest/runner-production.test.ts: 38 passed. All four focused files together onbd65ff2: 71 passed, 1 skipped.test/runner-workspace.test.ts -t 162): 3 passed onc578e81, whose code differs from the validated head only in tests, test comments, production code comments and the rule text. The volume checks count only volumes made with the probe's options, so other users of the daemon do not change them. Precondition, stated beside the test: no other probe runs on the same daemon at the same moment.caseSensitive true, unicodeNormalization noneon this host, whose APFS folds both, and leaves no container or volume.rm.rm, the settle window, the give-up). Those are checked only against the fake, whose answers were checked against Docker 27.4.0 by hand.pre-merge.test.ts, 96/96). This is recorded as infrastructure.testandreal-dockerpassed on these intermediate checkpoints:cbfd6dd,52dec12,f618181,3ae86fa,ce51c58,4250a65,70efd6f,6d5fac3,e20ea01,f361a98,4d2c8ba,3394355,5c90eb6,30454b4,609ed78.CASEor the NFD spelling. On Linux CI every such mutant fails one of the two script tests: the pre-seeded test catchesCaseandCAFE+ NFD, and the host-comparison test catchescaseand the NFC spelling. On macOS a mutant whose wrong name folds, under APFS's case and normalization folding, to a name in the test folder passes both (those four do). The real-Docker test catches only a mutant that changes tmpfs'strue noneanswer (caseand the NFC spelling do;CaseandCAFE+ NFD do not);CASE: caught on Linux CI by the one-name-per-field script tests; on macOS (APFS folds both) and in the real-Docker test it passes;false NFCfails a unit test on any host.false NFCitself fails only on Linux CI and in the real-Docker test, because macOS APFS answers it honestly;-cargument in place of the script;true nonewhatever the container answered;probeCleanupCallMsin either direction (exact values). A different deadline written at the call site is caught only if it moves the cap outside roughly 9–12 s or the floor outside roughly 0.4–2.5 s;rmafter the first absent answer;docker runinstead of from the end of the window;PROBE_SETTLE_MSandPROBE_CLEANUP_MS), and, on an unreachable daemon, a change of more than about 3 s to the time actually spent (timing test, 40–43 s);^anchor;rm --force's exit 0 without an inspect;rm;Reviews
7cd81a8/234486dcbfd6dd; staging-clone collapse filed as #1797cd81a8/cbfd6dd7cd81a8/cbfd6dd52dec12; rebase worktree added to #1797cd81a8/52dec12f618181: named container, removal on failure7cd81a8/f618181rm --forceexits 0 for a missing name, so the settle window never ran, and the test fake hid it. (b) A never-started client waited about 40 s and wrongly said a container may remain3ae86fa; the fake now answers as Docker 27 does (checked against real Docker)7cd81a8/3ae86face51c58and this body. Deferred: a recovery sweep for leftover probes, documented in the code and filed as #181 (see round 9)7cd81a8/ce51c5882c6c9bwith an exact-argv test. (b) declined: bounded, fails closed, keeps the original error first, and the code cannot tell a dead daemon from a refusal without matching more error text7cd81a8/82c6c9brmthat exits 0, so on a CLI that exits non-zero for a missing name it waits out the window and says a container may remain5ebd8e6: inspect after everyrm7cd81a8/5ebd8e6agents/labels.tsand its recovery filter, so it is filed as #181. The code comment points to #181 (4250a65)7cd81a8/4250a65PATHandDOCKER_HOST, so a daemon reachable only through a CLI context fails at startup now, as task storage already would7cd81a8/4250a657cd81a8/667d910inspectof a missing name prints nothing on stdout, while Docker 27.4.0 prints[], and its refused-run wording is not Docker's. (b) The CLI test's fake names no checked version. (c) The rule's "supported versions" is defined nowhere3590e0c, checked against real Docker 27.4.0. (c) reworded to "versions the code may meet" ine6cefdf, in both files7cd81a8/3590e0cnetwork lsandbuildb6d763f; the refused-run test now asserts the full message7cd81a8/b6d763fimage inspectorbuildamong its checked calls. (b) Round 4's audit row overstated coverage: the durable-record part of the cited rule is not met. (c) "After a daemon answer it checks once" is inaccurate; it retries while the container is presentd97a98a, checked against Docker 27.4.0. (b) reclassified in the audit as a disclosed deviation tracked in #181. (c) corrected in this body7cd81a8/d97a98ad2d47e0. (c) fixed in this audit7cd81a8/d2d47e0stuck;busyfor a missing container)54827ac: give-up checked before each call, latest outcome of either call reported, fake reworked. New tests count hung calls (1) and check the reported cause7cd81a8/54827ac76272c4to one helper that checks before every call, and a give-up that claims only "did not confirm … gone". (b) only a daemon refusal skips the settle window, with a new test. (c) fake note added. Validation section rewritten. Each fix is mutation-checked7cd81a8/76272c4rm(reproduced 1 in 3 under load). Low: (a) thelostfake's exit-125 text is not Docker's format. (b) The timing claim omits the 10 s cap per callbb6d5dc: the test accepts either last answer (3/3 passing); the fake uses the exact text Docker 27.4.0 prints for an unreachable daemon; the comment and this body name the cap7cd81a8/bb6d5dcunreachablefake answeredrmandinspectas a reachable daemon. (c) The old-CLI answer is not checked against a real CLI, and the audit did not record this; row 5b sat between two classes70efd6f, with the parse mutant now caught and the fake answering as Docker 27.4.0 does without a daemon. (c) recorded below; row 5b reclassified7cd81a8/70efd6fPATH_IDENTITY_SCRIPT; a constant passed as the-cargument passed every test (reproduced). Low: (a) row 5b's cited rule is about commands that succeeded, not ones that never started. (b) Theplan-library-alignment.mdrow lacked its evidenceacfb732: the argv test pins the exact script, and that mutant now fails. (a) 5b reclassified as a one-off. (b) evidence cited inacfb7327cd81a8/acfb732true nonepassed (reproduced). Low: the 10 s cap per cleanup call is untestedfde4e5a: the fake can answer any identity, each field is checked on its own, and a hung-daemon test shows the cap. Both mutants now fail7cd81a8/fde4e5a^anchor survives. (c) On Linux, nothing fails if the script writes the wrong names. (d) Four tests rely on a create landing before a 500 ms client deadline and do not assert it6d5fac3: floor, anchor and write-side tests, each mutant now fails (the write-side check on any host). The landing tests assert the create landed and use a 2 s deadline7cd81a8/6d5fac3rm, so the test never had a create land after cleanup found nothing; a mutant that stops sendingrmafter the first absent answer survived (reproduced). Low: audit rows 13a, 14b, 15c and 20a cite the claims rule, but they are about the audit itselfbde7ba5: the create lands after the first inspect, the test asserts the firstrmfound nothing and a later one removed it, and that mutant now fails. Low: those rows now cite the merge-gate rule7cd81a8/bde7ba5real-dockercheck covers the cleanup loop; no real-Docker test runs it. (c) Row 19c did not say the branch only partly meets its own new rulee20ea01. (b) and (c) corrected in this body; (c) is also stated beside the fake7cd81a8/e20ea01d39d624: the time the inspect was asked decides. A new fake state makes the sixth inspect slow while the create lands; that mutant now fails7cd81a8/d39d624f361a98: the fake marks the slow answer and the test asserts it (9 s sleep: about 1 s under the 10 s call cap, and at least 0.25 s, about 0.3–0.5 s in practice, after the window); a test pins the default bound (40–50 s, tightened in round 27); both mutants fail. (c) row added7cd81a8/f361a984d2c8ba: the ceiling is now 43 s, and both mutants fail. (b) row added7cd81a8/4d2c8ba3394355: the per-call deadline is the exportedprobeCleanupCallMs, with exact values tested in both directions. (b) row added7cd81a8/339435515dfb3d: the values are the named exportsPROBE_SETTLE_MSandPROBE_CLEANUP_MS, pinned exactly, and the mutation claim says what each test catches. The doc is back aboveremoveProbeContainer7cd81a8/15dfb3ddocker runpassed every test (reproduced). (b) The Validation section's commit labels and file list were incomplete5c90eb6: the late test requires the 2 s client deadline plus 10 s, and that mutant fails. (b) Validation section rewritten with baseline, validated head and intermediate-checkpoint labels7cd81a8/5c90eb67cd81a8/5c90eb6spawn); narrowing the check tospawn dockerpassed (reproduced)30454b4: an oversizedDOCKER_HOSTmakes the spawn throw, and the test asserts no wait and no Docker call; that mutant fails7cd81a8/30454b4c578e81: only volumes made with the probe's options count; 3 real-Docker tests passed on it7cd81a8/c578e8186b10ec), the Validation line and row 33b now state the precondition. (b) corrected in this body7cd81a8/86b10ec7cd81a8/86b10ecfalse NFCpasses every unit test on macOS (reproduced)911331b) and the mutation claim now say which constant each test catches, and where7cd81a8/911331bdocker runpassed (reproduced). (b) The settle window's end was pinned only from below; a 15 s window passed (reproduced), which also made the timing claim false as worded. (c) The "misnamesCASE" claim was unqualified; that mutant passes every unit test on macOS. (d) The new rule required fakes to reproduce output the code never readsac89ce1; both mutants fail. The timing claim now names its path. (c) qualified. (d) the rule now covers each part of an answer the code reads, with a note for what is left out (AGENTS.mdandCLAUDE.md, one commit)7cd81a8/ac89ce1609ed78). (c) accepted by the maintainer (@mchwang) on 2026-10-19 as part of the #181 gap; stated in the code comment and this body, and added to #1817cd81a8/609ed78CASEmutation claim was too broad. (b) The real-Docker line left out production code comments from the difference. (c) Row 25's one-off reason was false7cd81a8/609ed78CaseandCAFE+ NFD pass every unit test on macOS and the real-Docker test, and only Linux CI's pre-seeded test catches them (reproduced)7cd81a8/609ed78caseand the NFC spelling are caught by the host-comparison test, not the pre-seeded one7cd81a8/609ed78CASEand the NFD name seeded together, a script whose case check read the NFD name (or the reverse) passed every test on every host. (b) The busy-refusal test never showed the refusals were answered (it passed with the fake's busy branch off)0b74c05: one seeded name per field, so that swap fails on Linux CI (this macOS host cannot show it; the body says so); the busy test asserts the count reached 0 and a laterrmfound the container7cd81a8/0b74c05bd65ff2: the hung-daemon test's ceiling is 2.5 s, the 3.7 s mutant fails, and the comment and this body say roughly 0.4–2.5 s7cd81a8/bd65ff2rmonly), each failing a test; checked the fakes against Docker 27.4.0 and moby v27.4.0 source; and found every audit row classified and every quote verbatim7cd81a8/bd65ff2Latest clean independent-agent review: round 44, base
7cd81a8/ headbd65ff2. (Round 10 was clean on4250a65, now superseded; Copilot was clean there.) Copilot: clean on the same head (round 45). GitHubtestandreal-dockerpassed onbd65ff2.Review-lesson audit
Every finding above is classified below. "Claims rule" means the
AGENTS.md/CLAUDE.md"Review readiness" rule that starts "Treat every behavioural claim the change makes … give it a test that fails if the claim is false".AGENTS.md"Agent-controlled content": "Decide a filesystem fact … on the file system that will serve it, not on a copy"AGENTS.md"Owned host and Docker resources": "Treat the cleanup handle of an external resource … as owned state". The probe keeps and removes its handle, and fails startup closed naming it when removal does not settle. It does not record the handle durably, so a later startup does not find a leftover. That part of the rule is not met in this PR. It is tracked as #181; the leftover is inert (no process, unmounted tmpfs). Accepted by the maintainer (@mchwang) on 2026-10-19: merge with this documented gap; #181 stays a follow-uprm --forceexits 0 for a missing name, and the test fake hid itAGENTS.mdandCLAUDE.md(this branch): "A fake of an external tool … must give, for every call the code reads, each part of the answer the code reads (exit status, stdout, stderr) as the real tool gives it"rmthat exits 03590e0ce6cefdfb6d763fd97a98aAGENTS.md"Required race regressions": "A race regression must assert that the interleaving it names actually happened". The test relied on an order (inspect as the last call) that it never assertedunreachablefake's rm and inspectAGENTS.md"Owned host and Docker resources": "Treat the cleanup handle of an external resource … as owned state … fail closed until removal is confirmed". Accepted by the maintainer with the #181 gapCASEclaimremoveProbeContainercomment ate20ea01said "only an inspect that reports the name absent, after the settle window, proves the probe is gone", and a slow answer to an inspect asked inside the window made that falseThe new rule also applies to this branch's own fakes. The probe fake names the Docker version it was checked against (27.4.0) and gives the older CLI's
rmanswer (exit 1 for a missing name, before 20.10, per the docker/cli changelog; not run here). The CLI test's fake now says it models only a successful startup's calls.🤖 Generated with Claude Code