Skip to content

Probe task storage path identity at runner startup (#162) - #180

Merged
mchwang merged 40 commits into
mainfrom
fix/162-path-identity-probe
Oct 20, 2026
Merged

mchwang merged 40 commits into
mainfrom
fix/162-path-identity-probe

Conversation

@mchwang

@mchwang mchwang commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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

ReviewService derives plan path keys from the configured pathIdentity. 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.

  • What gets probed: task storage. That is the tmpfs work volume the agent edits, where the runner inspects and commits task changes. It lives in the Docker daemon's Linux kernel (agents/container/storage.ts), not in a folder under runner.root.
  • How it probes: probeTaskStoragePathIdentity(imageId) runs docker run --rm under a unique name (codeboost-path-identity-<uuid>), using the built image.
    • It is hardened like the keeper: read-only root, user 10001, no network, all capabilities dropped, no-new-privileges, the built-in seccomp profile, runc, and pid, memory and CPU limits.
    • Its /work is an anonymous local volume made with task storage's own tmpfs options. TASK_VOLUME_OPTIONS is now shared with allocation.
    • Once the container has started, the daemon removes it and its volume when it exits, even if the runner has died.
  • Cleanup on failure: a client stopped before the start (its deadline) can leave a container that was created but never started. --rm does not remove that, so a failed probe removes its container and volume by name.
    • Only container inspect reporting the name absent confirms removal. It runs after every rm, because rm --force's exit code for a missing name differs between Docker versions (27 exits 0).
    • Only a refusal from the daemon itself ("Error response from daemon") counts the first absent answer. After any other failure, only an absent answer to an inspect asked after a 10 s settle window counts: a killed client, or the client's own exit 125 (for example a daemon it cannot reach).
    • A client that never started sent nothing to the daemon, so it is neither removed nor waited for.
    • Removal gives up 30 s after the settle window. Each call gets the time left, capped at 10 s and at least 1 s, and after the first call none starts once that time has passed. A daemon that stops answering adds at most one call's 1 s floor, its stop grace and one 250 ms pause.
    • If removal does not settle, the startup error names the container to remove. Its cause carries "Docker did not confirm that is gone" with Docker's last outcome.
    • Not covered: a create that lands after the window, or a runner that dies before the removal. Nor is the volume's removal confirmed: --rm and rm --volumes remove 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).
  • When it runs: in 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.ts keeps Probe checkout path identity at runner startup #167's host probe for the demo, which reviews a host checkout. It now uses core/path-identity.ts's existing PathIdentity type and explicit \u escapes.

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 #assertCheckout already guards). That is #179, which includes a reproduction.

Validation

Labels: baseline 7cd81a8 (main). Validated head bd65ff2: the local gates below. Its GitHub test and real-docker checks 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.ts and test/runner-production.test.ts: 38 passed. All four focused files together on bd65ff2: 71 passed, 1 skipped.
  • Real Docker (test/runner-workspace.test.ts -t 162): 3 passed on c578e81, 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.
    • The probe reports caseSensitive true, unicodeNormalization none on this host, whose APFS folds both, and leaves no container or volume.
    • A never-started container made with the probe's exact mount loses its volume to the cleanup's own rm.
    • No real-Docker test runs the cleanup loop (inspect after every 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.
  • The local full unit suite is not a usable gate on this machine today. It had timeouts in files this PR does not touch, under load averages of 43–175 on 10 cores. Each affected file passed when run alone (for example pre-merge.test.ts, 96/96). This is recorded as infrastructure.
  • GitHub test and real-docker passed on these intermediate checkpoints: cbfd6dd, 52dec12, f618181, 3ae86fa, ce51c58, 4250a65, 70efd6f, 6d5fac3, e20ea01, f361a98, 4d2c8ba, 3394355, 5c90eb6, 30454b4, 609ed78.
  • Mutation checks, each of which fails at least one test:
    • removing the startup check;
    • a script that misnames CASE or the NFD spelling. On Linux CI every such mutant fails one of the two script tests: the pre-seeded test catches Case and CAFE + NFD, and the host-comparison test catches case and 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's true none answer (case and the NFC spelling do; Case and CAFE + NFD do not);
    • a script whose case check reads the NFD name, or whose normalization check reads 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;
    • a script that prints a constant. A constant other than false NFC fails a unit test on any host. false NFC itself fails only on Linux CI and in the real-Docker test, because macOS APFS answers it honestly;
    • a constant passed as the container's -c argument in place of the script;
    • returning true none whatever the container answered;
    • removing the 10 s cap per cleanup call;
    • removing the 1 s floor per cleanup call, or changing the floor or cap of probeCleanupCallMs in 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;
    • no further rm after the first absent answer;
    • judging the settle window by when an inspect answered;
    • starting the settle window when the client was started, not when it was stopped;
    • a settle window ending later than about 12.5 s (late-create test, upper bound);
    • counting the give-up from the start of docker run instead of from the end of the window;
    • a different settle window or default give-up value (exact-value test of PROBE_SETTLE_MS and PROBE_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);
    • dropping only the parse's ^ anchor;
    • a script that writes a wrong name (fails on any host, through the listing check);
    • removing the failure cleanup;
    • trusting rm --force's exit 0 without an inspect;
    • dropping the never-started shortcut, or narrowing it to the asynchronous spawn error;
    • dropping the tmpfs volume options;
    • skipping the inspect after a failed rm;
    • a fixed 10 s call deadline;
    • starting calls after the give-up time;
    • treating any numeric exit as a daemon refusal;
    • dropping the identity parse's anchors.

Reviews

Round Type / reviewer Base / head Findings What changed / declined
1 Self-review 7cd81a8 / 234486d The probe targets the host staging folder, not task storage; it refuses the correct setting on macOS Fixed in cbfd6dd; staging-clone collapse filed as #179
2 Self-review 7cd81a8 / cbfd6dd None
3 Independent agent (general-purpose subagent) 7cd81a8 / cbfd6dd (a) Docs overclaim: the rebase worktree is a host checkout too. (b) No test fails if the probe stops measuring. (c) A test comment claims an order the test does not check All fixed in 52dec12; rebase worktree added to #179
4 Independent agent (fresh subagent) 7cd81a8 / 52dec12 A client killed between create and start leaks an unnamed container and volume Fixed in f618181: named container, removal on failure
5 Independent agent (fresh subagent) 7cd81a8 / f618181 (a) rm --force exits 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 remain Both fixed in 3ae86fa; the fake now answers as Docker 27 does (checked against real Docker)
6 Independent agent (fresh subagent) 7cd81a8 / 3ae86fa No production bugs. (a) No negative control on Linux CI. (b) Retry, give-up and not-started paths untested. (c) Comment overclaims removal. (d) PR body stale; "hardened like the seeder" is wrong All fixed in ce51c58 and this body. Deferred: a recovery sweep for leftover probes, documented in the code and filed as #181 (see round 9)
7 Independent agent (fresh subagent) 7cd81a8 / ce51c58 No correctness bugs. (a) No test fails if the probe stops using task storage's volume options or the keeper's hardening. (b) If the daemon stops answering after the image build, cleanup stalls 30 s and says a container may remain (a) fixed in 82c6c9b with 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 text
8 Independent agent (fresh subagent) 7cd81a8 / 82c6c9b No correctness bugs. Low: cleanup confirms by inspect only after an rm that exits 0, so on a CLI that exits non-zero for a missing name it waits out the window and says a container may remain Fixed in 5ebd8e6: inspect after every rm
9 Independent agent (fresh subagent) 7cd81a8 / 5ebd8e6 No correctness bugs. Low: the round-6 reason for not sweeping leftover probes holds only for an unscoped label; a sweep scoped to the per-database runner owner token, under the runner lock, could not touch another runner's probe Reason corrected here. The sweep needs a runner-scoped label shape in agents/labels.ts and its recovery filter, so it is filed as #181. The code comment points to #181 (4250a65)
10 Independent agent (fresh subagent) 7cd81a8 / 4250a65 No findings. It confirmed the declined (round 7b) and deferred (#181) reasons hold. It also noted, as existing behaviour and not a finding, that Docker reaches only PATH and DOCKER_HOST, so a daemon reachable only through a CLI context fails at startup now, as task storage already would
11 Copilot (copilot-pull-request-reviewer) 7cd81a8 / 4250a65 No issues; no inline comments Requested after the round 10 clean pass on the same head
12 Independent agent (fresh subagent) 7cd81a8 / 667d910 No correctness bugs. Rule compliance: (a) the probe fake's inspect of 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 nowhere Fixed: (a) and (b) in 3590e0c, checked against real Docker 27.4.0. (c) reworded to "versions the code may meet" in e6cefdf, in both files
13 Independent agent (fresh subagent) 7cd81a8 / 3590e0c No correctness bugs. (a) The audit quoted the rule's old wording. (b) The refused-run fake lacked Docker's second stderr line. (c) The CLI fake's list of modelled calls missed network ls and build (a) fixed in this body. (b) and (c) fixed in b6d763f; the refused-run test now asserts the full message
14 Independent agent (fresh subagent) 7cd81a8 / b6d763f No correctness bugs. (a) Neither fake listed image inspect or build among 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 present (a) fixed in d97a98a, checked against Docker 27.4.0. (b) reclassified in the audit as a disclosed deviation tracked in #181. (c) corrected in this body
15 Independent agent (fresh subagent) 7cd81a8 / d97a98a No correctness bugs. Medium: round 4's rule is not fully met (no durable record of a leftover probe). Low: (a) the cleanup bound does not hold when the daemon hangs; (b) volume removal on the failure path is untested; (c) audit rows 9 and 13a misclassified; (d) the runner lifecycle doc and README omit the startup check Medium: the maintainer accepted the gap; recorded in the audit, #181 stays open. (a), (b) and (d) fixed in d2d47e0. (c) fixed in this audit
16 Independent agent (fresh subagent) 7cd81a8 / d2d47e0 No correctness bugs. Low: (a) the stated bound is false: a pass can start after the give-up time and make two calls (measured about 2 s over). (b) The give-up cause can name a stale "No such container" answer when the daemon later stops answering. (c) Two fake states Docker never answers (stuck; busy for a missing container) All fixed in 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 cause
17 Independent agent (fresh subagent) 7cd81a8 / 54827ac No Medium or High findings. Low: (a) the give-up cause can still name a stale "present" answer. (b) A client-side exit 125 (lost connection after the create was sent) skips the settle window. (c) The probe fake prints less stdout than Docker for an existing container, with no note on what is read. (d) No test for the top-of-loop give-up check, and the Validation section was stale (a) and (d): cleanup simplified in 76272c4 to 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-checked
18 Independent agent (fresh subagent) 7cd81a8 / 76272c4 No production bugs. Medium: the give-up test is flaky; it assumed the inspect is always the last call, but giving up can fall due during the rm (reproduced 1 in 3 under load). Low: (a) the lost fake's exit-125 text is not Docker's format. (b) The timing claim omits the 10 s cap per call All fixed in bb6d5dc: 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 cap
19 Independent agent (fresh subagent) 7cd81a8 / bb6d5dc No correctness bugs. Low: (a) no test covers the strict parse (a mutant that drops its anchors passes). (b) The unreachable fake answered rm and inspect as 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 classes (a) and (b) fixed in 70efd6f, with the parse mutant now caught and the fake answering as Docker 27.4.0 does without a daemon. (c) recorded below; row 5b reclassified
20 Independent agent (fresh subagent) 7cd81a8 / 70efd6f No correctness bugs. Medium: nothing checked that the container runs PATH_IDENTITY_SCRIPT; a constant passed as the -c argument passed every test (reproduced). Low: (a) row 5b's cited rule is about commands that succeeded, not ones that never started. (b) The plan-library-alignment.md row lacked its evidence Medium fixed in acfb732: the argv test pins the exact script, and that mutant now fails. (a) 5b reclassified as a one-off. (b) evidence cited in acfb732
21 Independent agent (fresh subagent) 7cd81a8 / acfb732 No correctness bugs. Medium: no test checks the values the probe reports; a probe that always returned true none passed (reproduced). Low: the 10 s cap per cleanup call is untested Both fixed in fde4e5a: the fake can answer any identity, each field is checked on its own, and a hung-daemon test shows the cap. Both mutants now fail
22 Independent agent (fresh subagent) 7cd81a8 / fde4e5a No correctness bugs. Low: (a) the 1 s floor per call is untested. (b) Dropping only the parse's ^ 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 it All fixed in 6d5fac3: 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 deadline
23 Independent agent (fresh subagent) 7cd81a8 / 6d5fac3 No correctness bugs. Medium: the late-create fake landed before the first rm, so the test never had a create land after cleanup found nothing; a mutant that stops sending rm after the first absent answer survived (reproduced). Low: audit rows 13a, 14b, 15c and 20a cite the claims rule, but they are about the audit itself Medium fixed in bde7ba5: the create lands after the first inspect, the test asserts the first rm found nothing and a later one removed it, and that mutant now fails. Low: those rows now cite the merge-gate rule
24 Independent agent (fresh subagent) 7cd81a8 / bde7ba5 No correctness bugs. Low: (a) the "last outcome" test does not assert that an absent answer came before the hang, and a comment's timing is wrong. (b) The body claimed the GitHub real-docker check covers the cleanup loop; no real-Docker test runs it. (c) Row 19c did not say the branch only partly meets its own new rule (a) fixed in e20ea01. (b) and (c) corrected in this body; (c) is also stated beside the fake
25 Independent agent (fresh subagent) 7cd81a8 / e20ea01 No Medium or High findings. Low: the settle window is judged by when the inspect answered, not when it was asked. A slow absent answer to an ask made inside the window, arriving after it while a create lands, was taken as settled and left the container Fixed in d39d624: the time the inspect was asked decides. A new fake state makes the sixth inspect slow while the create lands; that mutant now fails
26 Independent agent (fresh subagent) 7cd81a8 / d39d624 No correctness bugs. Low: (a) the slow-inspect test did not assert that the slow inspect answered (under load it could be cut at its deadline). (b) The default 30 s give-up has no test. (c) Round 19's row 5b part was not classified (a) and (b) fixed in f361a98: 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 added
27 Independent agent (fresh subagent) 7cd81a8 / f361a98 No correctness bugs. Low: (a) the default-bound test caught only a shorter bound; a 38 s give-up or a 15 s settle window passed under its 50 s ceiling (reproduced). (b) Round 19(c)'s "the audit did not record that" part had no row (a) fixed in 4d2c8ba: the ceiling is now 43 s, and both mutants fail. (b) row added
28 Independent agent (fresh subagent) 7cd81a8 / 4d2c8ba No correctness bugs. Low: (a) the 1 s floor and 10 s cap were pinned from one side only; a 500 ms or 3 s floor and an 11.5 s cap passed (reproduced). (b) Round 24(a)'s wrong-comment part had no row (a) fixed in 3394355: the per-call deadline is the exported probeCleanupCallMs, with exact values tested in both directions. (b) row added
29 Independent agent (fresh subagent) 7cd81a8 / 3394355 No correctness bugs. Low: (a) the mutation list claimed any change to the default give-up or settle window fails, but changes under about 3 s passed the timing test (reproduced: 32 s, 12 s). (b) A JSDoc block sat above the wrong declaration Both fixed in 15dfb3d: the values are the named exports PROBE_SETTLE_MS and PROBE_CLEANUP_MS, pinned exactly, and the mutation claim says what each test catches. The doc is back above removeProbeContainer
30 Independent agent (fresh subagent) 7cd81a8 / 15dfb3d No correctness bugs. Low: (a) no test checks when the settle window starts; measuring it from the start of docker run passed every test (reproduced). (b) The Validation section's commit labels and file list were incomplete (a) fixed in 5c90eb6: 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 labels
31 Independent agent (fresh subagent) 7cd81a8 / 5c90eb6 No correctness, test or code findings. Low (audit): 15d and the file-list part of 30b were not classified on their own Both rows added to this body; the head is unchanged
32 Independent agent (fresh subagent) 7cd81a8 / 5c90eb6 No correctness bugs. Low: no test covers the synchronous spawn failure (E2BIG, syscall spawn); narrowing the check to spawn docker passed (reproduced) Fixed in 30454b4: an oversized DOCKER_HOST makes the spawn throw, and the test asserts no wait and no Docker call; that mutant fails
33 Independent agent (fresh subagent) 7cd81a8 / 30454b4 No Medium or High findings. Low: (a) a comment and a mutation claim said the timing tests pin the call's deadline, but they pin it only roughly (an 11 s cap or 2 s floor written at the call site passed; reproduced). (b) The real-Docker volume checks compared every volume on the daemon, so another run there could fail them (a) the comment and the claim now state the tolerance. (b) fixed in c578e81: only volumes made with the probe's options count; 3 real-Docker tests passed on it
34 Independent agent (fresh subagent) 7cd81a8 / c578e81 No correctness bugs. Low: (a) the claim that another run on the daemon cannot change the real-Docker result is false: another probe at the same moment matches the same options. (b) Round 26's "about 1 s margin each side" overstates the lower margin (about 0.3–0.5 s) (a) the comment (86b10ec), the Validation line and row 33b now state the precondition. (b) corrected in this body
35 Independent agent (fresh subagent) 7cd81a8 / 86b10ec No correctness, test or code findings. Low (audit): row 18's quote was cut so that the rule's ending ("that still passes") was hidden; that ending is about tests that pass falsely, not ones that fail falsely Row 18 now quotes the rule's first sentence, which covers a test relying on an order it never asserts. The head is unchanged
36 Independent agent (fresh subagent) 7cd81a8 / 86b10ec No correctness findings. Low: a test comment and a mutation claim said a constant-printing script fails on any host; a script printing false NFC passes every unit test on macOS (reproduced) The comments (911331b) and the mutation claim now say which constant each test catches, and where
37 Independent agent (fresh subagent) 7cd81a8 / 911331b No correctness bugs. Low: (a) the give-up's start point is untested; counting it from the start of docker run passed (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 "misnames CASE" claim was unqualified; that mutant passes every unit test on macOS. (d) The new rule required fakes to reproduce output the code never reads (a) and (b) fixed in ac89ce1; 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.md and CLAUDE.md, one commit)
38 Independent agent (fresh subagent) 7cd81a8 / ac89ce1 No correctness bugs. Low: (a) row 5a quoted the rule's old wording. (b) The overshoot bound left out one 250 ms pause. (c) The volume's removal is never confirmed; a Docker-side failure to remove it leaves an unlabelled volume and no "may remain". (d) Rows 25 and 17d cited rules that do not cover them in full (a), (b) and (d) corrected in this body; (b) also in the comment (609ed78). (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 #181
39 Independent agent (fresh subagent) 7cd81a8 / 609ed78 No correctness, test or code findings. Low (PR body): (a) the misnamed-CASE mutation 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 false All three corrected in this body; the head is unchanged
40 Independent agent (fresh subagent) 7cd81a8 / 609ed78 No correctness, test or audit findings. Low (PR body): the misnamed-spelling mutation claim was still too broad; Case and CAFE + NFD pass every unit test on macOS and the real-Docker test, and only Linux CI's pre-seeded test catches them (reproduced) The claim now says, per mutant, where it is caught. The head is unchanged
41 Independent agent (fresh subagent) 7cd81a8 / 609ed78 No code, test, fake, rule-sync or audit findings. Low (PR body): on Linux CI case and the NFC spelling are caught by the host-comparison test, not the pre-seeded one Bullet corrected in this body; the head is unchanged
42 Independent agent (fresh subagent) 7cd81a8 / 609ed78 No correctness bugs. Low: (a) with CASE and 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) Both fixed in 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 later rm found the container
43 Independent agent (fresh subagent) 7cd81a8 / 0b74c05 No correctness bugs. Low: the stated floor tolerance was wrong; a 3.7 s floor at the call site passed every test (reproduced) Fixed in bd65ff2: 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 s
44 Independent agent (fresh subagent) 7cd81a8 / bd65ff2 No findings. It ran mutants at the edges of every stated tolerance (2.6 s and 0.3 s floors, 8.8 s cap, 12.6 s settle window, a give-up check before rm only), 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 verbatim
45 Copilot (copilot-pull-request-reviewer) 7cd81a8 / bd65ff2 No issues; no inline comments Requested after the round 44 clean pass on the same head

Latest clean independent-agent review: round 44, base 7cd81a8 / head bd65ff2. (Round 10 was clean on 4250a65, now superseded; Copilot was clean there.) Copilot: clean on the same head (round 45). GitHub test and real-docker passed on bd65ff2.

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".

Round / finding Class Rule or reason
1: the probe targets the host staging folder, not task storage Covered by an existing rule AGENTS.md "Agent-controlled content": "Decide a filesystem fact … on the file system that will serve it, not on a copy"
3a: docs overclaim (the rebase worktree is a host checkout) Covered by an existing rule Claims rule
3b: no test fails if the probe stops measuring Covered by an existing rule Claims rule
3c: a test comment claims an order the test does not check Covered by an existing rule Claims rule
4: a client killed between create and start leaks the container Covered by an existing rule — not fully met by this PR 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-up
5a: rm --force exits 0 for a missing name, and the test fake hid it Captured by a new rule New "Review readiness" rule in AGENTS.md and CLAUDE.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"
5b: a never-started client waited about 40 s and named a container One-off It is about mapping this probe's own subprocess outcomes (a client that never started sent nothing). No existing rule covers that, and it is too narrow to generalise
6a: no negative control on Linux CI Covered by an existing rule Claims rule: the test must fail when the claim is false, on the platform CI runs
6b: retry, give-up and not-started paths untested Covered by an existing rule Claims rule ("bounded by N seconds")
6c: the comment overclaims removal Covered by an existing rule Claims rule
6d: stale PR body; "hardened like the seeder" Covered by an existing rule Claims rule, and "Keep final exact-head results in a place that can name the resulting commit"
7a: no test pins the volume options or hardening Covered by an existing rule Claims rule
7b: a dead daemon stalls cleanup for 30 s (declined) One-off Bounded and fails closed; reason recorded in round 7
8: inspect only after an rm that exits 0 Captured by a new rule The same new rule: "Where versions the code may meet answer differently … the code must not depend on that answer"
9: the decline reason holds only for an unscoped label Covered by an existing rule Claims rule: the decline reason is a claim in the PR body. The sweep it pointed to is filed as #181
10, 11 (Copilot) No findings
12a, 12b: this branch's fakes did not fully meet the new rule Captured by a new rule The new fakes rule itself; fixed in 3590e0c
12c: "supported versions" is undefined One-off Wording of the new rule, corrected in e6cefdf
13b, 13c: fake stderr and the CLI fake's call list Captured by a new rule The new fakes rule; fixed in b6d763f
13a: stale quote in this audit Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
14a: fakes' call lists incomplete Captured by a new rule The new fakes rule; fixed in d97a98a
14b: audit coverage overstated Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
14c: PR-body claim inaccurate Covered by an existing rule Claims rule ("Treat every behavioural claim … in code comments, the PR body or docs")
15 Medium: durable record of a leftover probe Covered by an existing rule — not fully met As row 4; accepted by the maintainer, #181
15a, 15b: cleanup bound, volume-removal test Covered by an existing rule Claims rule ("bounded by N seconds"; a test that fails if the claim is false)
15d: the runner lifecycle doc and README omitted the new startup step One-off Nothing they said was false; they were incomplete for this change. No rule requires updating docs that only omit a step, and a rule for it would mostly repeat "Treat every behavioural claim"
15c: audit classification Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
16a, 16b: cleanup bound and give-up cause Covered by an existing rule Claims rule ("bounded by N seconds")
16c: fake states Docker never answers Captured by a new rule The new fakes rule
17a, 17b, 17d (claims part): give-up cause, settle on client exit 125, untested check Covered by an existing rule Claims rule
17d (record part): the Validation section was stale Covered by an existing rule "Keep final exact-head results in a place that can name the resulting commit, such as the PR body or CI record"
17c: fake stdout note Captured by a new rule The new fakes rule
18 Medium: flaky give-up test Covered by an existing rule AGENTS.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 asserted
18a: fake exit-125 text Captured by a new rule The new fakes rule
18b: timing claim Covered by an existing rule Claims rule
19a: strict parse untested Covered by an existing rule Claims rule ("a probe that fails stops startup")
19b: unreachable fake's rm and inspect Captured by a new rule The new fakes rule
20 Medium: the container's script was not pinned Covered by an existing rule Claims rule ("give it a test that fails if the claim is false"); the same class as 3b
20a: audit classification Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
20b: doc row without evidence Covered by an existing rule Claims rule: the row's claim ("probed at runner startup since #162") in docs
21 Medium, 21a: reported values and call cap untested Covered by an existing rule Claims rule; the same class as 3b and 20
22a, 22b, 22c: floor, anchor, script writes untested Covered by an existing rule Claims rule
23 Medium: late-create interleaving not exercised Covered by an existing rule "Required race regressions": "A race regression must assert that the interleaving it names actually happened"
24a: interleaving not asserted Covered by an existing rule "Required race regressions": "A race regression must assert that the interleaving it names actually happened"
24a, comment part: a test comment's timing was wrong Covered by an existing rule Claims rule ("Treat every behavioural claim … in code comments")
28a: floor and cap pinned from one side Covered by an existing rule Claims rule
28b: audit row missing Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
29a, 29b: mutation claim and misplaced doc Covered by an existing rule Claims rule ("in code comments, the PR body or docs")
30a: settle-window start untested Covered by an existing rule Claims rule
30b, labels: commit labels incomplete Covered by an existing rule "In evidence records, label cited commits as baselines, intermediate checkpoints, or validated heads"
30b, file list: an affected test file was missing from the results Covered by an existing rule "Validation evidence": "Report focused tests, affected-suite tests, the full suite, type checking, CI and real-Docker validation as separate gates"
43: floor tolerance Covered by an existing rule Claims rule
42a: field-swap mutant uncaught Covered by an existing rule Claims rule
42b: refusals not asserted Covered by an existing rule "Required race regressions": "A race regression must assert that the interleaving it names actually happened"
41: mutation claim Covered by an existing rule Claims rule
40: mutation claim Covered by an existing rule Claims rule
39a, 39b: mutation claim and evidence record Covered by an existing rule Claims rule; "Keep final exact-head results in a place that can name the resulting commit, such as the PR body or CI record"
39c: audit classification Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
38a, 38d: stale quote and citations Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
38b: overshoot bound Covered by an existing rule Claims rule ("bounded by N seconds")
38c: volume removal not confirmed Covered by an existing rule — not fully met AGENTS.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 gap
37a, 37b, 37c: give-up start, window end, CASE claim Covered by an existing rule Claims rule
37d: wording of the new rule Captured by a new rule The new fakes rule, reworded in this branch
36: constant-script claim Covered by an existing rule Claims rule ("in code comments, the PR body or docs")
35: audit quote cut Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
34a, 34b: precondition and margin claims Covered by an existing rule Claims rule
33a: tolerance overstated Covered by an existing rule Claims rule
33b: real-Docker check sees the whole daemon One-off These two new tests listed every volume on the daemon; the repository's other Docker-backed tests already find their objects by label or name. Scoping them to volumes made with the probe's options removes other daemon users; another probe at the same moment would still match, which is stated beside the test as a precondition. A rule would only restate that tests must check the objects they made
32: synchronous spawn failure untested Covered by an existing rule Claims rule ("A client that never started … is neither removed nor waited for")
31: audit rows missing Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
24b: PR-body claim Covered by an existing rule Claims rule
24c: audit classification Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
25: settle judged by answer time Covered by an existing rule Claims rule: the removeProbeContainer comment at e20ea01 said "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 false
19c, audit part: the audit did not record the unchecked old-CLI answer Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
27a: bound pinned from one side only Covered by an existing rule Claims rule ("bounded by N seconds")
27b: audit row missing Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
19c, second part: row 5b sat between classes Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
26a: interleaving not asserted Covered by an existing rule "Required race regressions": "A race regression must assert that the interleaving it names actually happened"
26b: default bound untested Covered by an existing rule Claims rule ("bounded by N seconds")
26c: audit row missing Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
23a: audit citations Covered by an existing rule Merge-gate rule: "Treat review-lesson extraction as a merge gate. Before invoking merge, classify every review finding in the PR body … (cite it)"
22d: landing interleaving not asserted Covered by an existing rule "Required race regressions": "A race regression must assert that the interleaving it names actually happened"
19c: old-CLI answer not run Captured by a new rule — not fully met The new fakes rule. The one answer that could not be checked against a real tool, because no pre-20.10 CLI is available here, is checked against the Docker 20.10.0 release notes (docker/cli#2678) and says so beside the fake. Its stderr text is Docker 27's wording and is unchecked, and it can become the reported give-up cause. The code does not branch on it, because inspect decides

The 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 rm answer (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

eastagiletracker and others added 2 commits October 19, 2026 10:36
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>
mchwang and others added 6 commits October 19, 2026 12:00
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>

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, 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

mchwang and others added 15 commits October 19, 2026 14:28
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>
mchwang and others added 16 commits October 19, 2026 18:18
…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 38. The overshoot bound includes one
250 ms pause. The volume's removal is Docker's and is not confirmed;
the maintainer accepted that as part of #181.

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>

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 unresolved blocking issues remain; host-checkout limitations and accepted cleanup gaps are documented and tracked separately.

No open issues 🎉

Review effort: Balanced

@mchwang
mchwang merged commit eafe2be into main Oct 20, 2026
3 checks passed
@mchwang
mchwang deleted the fix/162-path-identity-probe branch October 20, 2026 19:19
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 checkout path identity at runner startup

3 participants