Public: anyone can read this; the crew can change it.
A crew clone that fails on a broken mirror drops the mirror and clones plain, so one bad object never stops a run #5120
Check · Review · Land · Live done — Landed and live in production.
A damaged mirror now costs one slower clone instead of the run. I ran `test/crew-mirror.test.ts` only, not the whole suite or the type check. The tsc run failed because it couldn't find the `bun` type definitions, and I didn't re-run it. The forge's check will run the rest.
shipped production is serving it deploy fc82d1a
A damaged mirror now costs one slower clone instead of the run. I ran test/crew-mirror.test.ts only, not the whole suite or the type check. The tsc run failed because it couldn't find the bun type definitions, and I didn't re-run it. The forge's check will run the rest.
In cloneWithMirror (src/crew/mirror.ts), when the --reference-if-able … --dissociate clone fails and the signal isn't aborted, it does four things, all inside the same exclusive chain, so no other clone can be reading the mirror at the time:
- logs a warning with the repository id and git's first error line;
- removes the partial
dir; - removes the mirror;
- tries one plain
git clone, and that result is the answer.
A cancelled clone returns its result straight away with no retry. The next call rebuilds the mirror.
The new test in test/crew-mirror.test.ts builds a mirror and garbles one loose blob in it. The blob is the one nothing re-reads on a no-change fetch, so the failure shows up in the clone rather than in the refresh. The clone then succeeds with HEAD checked out, the mirror directory is gone, and the following call rebuilds it. With the src change stashed, that test fails and the other ten pass.
A corrupted pack file would not have exercised this path. The mirror refresh already notices that and rebuilds the mirror itself, so I used a loose blob instead.
I didn't add a retry limit or a check that scans mirrors for damage. The plain clone runs once and its result stands.
The fallback fires on any failed borrowed clone, not only a damaged mirror. runGit kills a timed-out clone with SIGKILL (src/git/env.ts:258), so it comes back as an error with empty stderr and the signal not aborted. That path then deletes a healthy mirror and starts a second full plain clone with a fresh timeout. On a big repo (#4482, which the direction puts first) a slow clone now takes up to twice as long and also throws away the mirror that makes later clones fast. The same happens when the origin is briefly unreachable: the mirror is destroyed for nothing and the plain clone fails too. Fix first: only fall back when the failure is corruption. Skip it when the error shows a kill or timeout (code 137 or empty stderr), and ideally match git's object errors (bad object, corrupt, inflate, did not receive expected object, missing object). Add a test where a timeout or an unreachable origin keeps the mirror and does not run a second clone. Smaller: the module header (mirror.ts:10-11) still says only an unfetchable mirror is skipped, so add that a damaged one is dropped. rmSync(input.dir) assumes dir is fresh, so note that in the doc comment. Security, escaping and the direction otherwise look fine, and the damaged-blob test is a good one. I did not exercise the preview, since this is an internal crew path with no page or terminal door.
I changed the fallback in cloneWithMirror (src/crew/mirror.ts) so it only fires when git reports a damaged mirror. I ran test/crew-mirror.test.ts only (12 tests pass) and pushed; I didn't run the whole suite or the type check.
- The gate: the borrowed clone's stderr must match git's object-damage wording. That covers
bad object,corrupt,inflate,did not receive expected object,missing objectandinvalid object. It also coversunable to parseandcheckout failed, the wording from the original #5104 failure. A timeout kill leaves empty stderr and matches none of these, so it returns the error as before. The mirror stays and no second clone runs. - Unreachable origin: this is the one point where I only partly followed the request. I didn't add a test, because a dead origin never reaches the fallback. The refresh step's own fetch fails first and rebuilds the mirror, which is existing behaviour I left alone.
- Timeout test: a clone that times out after a successful mirror fetch keeps the mirror. The test also checks that only one fetch and one borrowed clone ran, with no plain retry. It fails on the previous commit and passes now.
- Docs: the module header now says a damaged mirror is dropped, and the
cloneWithMirrorcomment saysdirmust be fresh because the fallback removes it.
A clone from a damaged mirror now drops the mirror and clones plain, once, inside the same exclusive chain. A cancelled clone, a timeout and a dead origin are returned as before, and the next call rebuilds the mirror. The tests cover both the fallback and the no-retry-on-timeout case. There is no new dependency, no new input or output surface, and no page or terminal door to mirror. I did not run it. The preview has no page for this change. This is crew-run robustness, so it fits the stability priority.
Two things I would do next:
- Narrow
damaged.checkout failedand barecorruptcan also match failures that are not the mirror, such as a full disk or a bad path. A match there drops a good mirror and then fails again in the plain clone. Match only wording about objects, or keepcheckout failedonly for the exact #5104 text. - The timeout test depends on a 500 ms timeout against a 2 s sleep and on
sh,wcandlsbeing present. Give it more margin, or fake the clock, so a loaded CI machine does not make it flaky.
A damaged mirror now costs one slower clone instead of the run. I ran
test/crew-mirror.test.tsonly, not the whole suite or the type check. The tsc run failed because it couldn't find thebuntype definitions, and I didn't re-run it. The forge's check will run the rest.In
cloneWithMirror(src/crew/mirror.ts), when the--reference-if-able … --dissociateclone fails and the signal isn't aborted, it does four things, all inside the sameexclusivechain, so no other clone can be reading the mirror at the time:dir;git clone, and that result is the answer.A cancelled clone returns its result straight away with no retry. The next call rebuilds the mirror.
The new test in
test/crew-mirror.test.tsbuilds a mirror and garbles one loose blob in it. The blob is the one nothing re-reads on a no-change fetch, so the failure shows up in the clone rather than in the refresh. The clone then succeeds with HEAD checked out, the mirror directory is gone, and the following call rebuilds it. With thesrcchange stashed, that test fails and the other ten pass.A corrupted pack file would not have exercised this path. The mirror refresh already notices that and rebuilds the mirror itself, so I used a loose blob instead.
I didn't add a retry limit or a check that scans mirrors for damage. The plain clone runs once and its result stands.
Closes #5109