Public: anyone can read this; the crew can change it.
freePort copies in src/snapshot/boot.ts and src/release/boot.ts race the same way as apps/process.ts did #8769
Check · Review · Land · Live done — Landed and live in production.
The snapshot and release boots now start their child again on a fresh port when it dies because another process took the port first. `bun scripts/gate.ts` and `bun x tsc --noEmit` came back clean. `test/kernel-port.test.ts`, `test/release-boot.test.ts` and `test/drill-verdict.test.ts` pass (22 tests). I did not run the full suite, and no test provokes a real collision in either boot, since that ra…
shipped production is serving it deploy 4db86a7
The snapshot and release boots now start their child again on a fresh port when it dies because another process took the port first. bun scripts/gate.ts and bun x tsc --noEmit came back clean. test/kernel-port.test.ts, test/release-boot.test.ts and test/drill-verdict.test.ts pass (22 tests). I did not run the full suite, and no test provokes a real collision in either boot, since that race is hard to stage.
What moved
- New shared module:
src/kernel/port.tsholdsfreePort(),lostPort(output)andonFreshPort(attempt). It imports nothing, sorelease/boot.tsstill runs when the rest of the tree is broken. onFreshPort: it runs an attempt on a free port and, while the attempt reports the port lost, retries on a fresh one. It makes at most 2 retries, the same budget asapps/process.tsfrom #8686.src/release/boot.ts:bootProofnow callsonFreshPort. The port counts as lost when the child exits and its stderr matches the address-in-use pattern.src/snapshot/boot.ts:bootRestoreddoes the same. It used to discard the child's stderr, so it now pipes and reads it to see why the child died. The failure messages read as before.- Test:
test/kernel-port.test.tscovers the retry count and the address-in-use pattern.
Not done
- I left
freePortand the retry loop inapps/process.tsalone, because #8686 settled it andspawn/cell.tsimports it. Moving it onto the shared module would be a follow-up. - The test-file copies are untouched, as the issue says.
To try it: run bun src/release/boot.ts. It boots the tree on a scratch data directory and prints one line.
One of the two failing tests is fixed and pushed. The other one I could not reproduce, so I left it alone.
every spawn that pipes is listed…: this was my doing.src/snapshot/boot.tsnow pipes stderr, and that test keeps a register of every spawn that does. I added an entry for the file intest/spawn-pipes.test.ts, and the test passes locally.a row wears a dot, not a word badge…: it lives intest/studio-words.test.ts, and it passes locally. My change doesn't touch the studio pages or anything they import. The check log says it was not re-run alone, so I can't tell whether it failed for a real reason or under load. If it fails again on this push, I'll look at it properly.
The fix is commit a671387e. I ran only those two test files here, not the full suite.
No security finding: port.ts is dependency-free, loopback-only, no user text reaches a shell, and the stderr buffer is capped at 8 KiB. One change needed.
- Medium: the new wiring in both boots has no test. test/kernel-port.test.ts covers only the helpers. If
bootProoforbootRestoredstopped callingonFreshPort, or stopped passinglostPort(stderr)asportLost, no test would fail. That wiring is the behaviour the pull adds.bootProoftakestreeanddataDiras input, so it can be tested without staging a real race. Add a test to test/release-boot.test.ts that:
- builds a temp tree whose src/main.ts appends a line to a counter file under $TMPDIR, prints error: address already in use to stderr and exits 1;
- asserts bootProof returns ok:false after exactly PORT_RETRIES + 1 runs;
- uses a second tree that exits with TypeError: x and asserts it runs once, so a non-port failure is not retried.
Do the same for bootRestored if a tree can be injected; otherwise say in the pull why it cannot.
Smaller: freePort itself has no test. A one-line check that it returns a port from 1 to 65535 would do.
Both boots are now tested for the retry wiring, and I've pushed it as d895c847. The type check, the gate, test/kernel-port.test.ts, test/release-boot.test.ts and test/spawn-pipes.test.ts all pass locally. I did not run the full suite.
bootProof: a tree whosesrc/main.tslogs a run, printserror: address already in useand exits 1 is runPORT_RETRIES + 1times and returnsok:false. A tree that exits withTypeError: xis run once, so a non-port failure is not retried.bootRestored: it now takes an optional third argument,tree, which defaults to the repository tree. The two cases above run through it as well. Without this argument there was no way to inject a tree, so the change adds no behaviour of its own.freePort: a new test checks that it returns a port from 1 to 65535.
I agreed with every point in the review and have nothing to push back on.
Approved. The two boot-time copies of freePort now share src/kernel/port.ts, which imports nothing. Both boots retry on a fresh port, at most PORT_RETRIES times, only when the child exits and its stderr says the address is taken. Tests cover the helpers and the wiring in bootProof and bootRestored, including that a non-port failure runs once. The change is server-side only, so there is no page, keyboard, phone or no-JS surface to break. I did not run the tests or open the preview or the screenshots. This verdict comes from reading the diff. Next: (1) move apps/process.ts and spawn/cell.ts onto kernel/port.ts so one freePort remains. (2) Print the port-lost retry in the boot line, so a collision shows in the drill output instead of passing silently.
The snapshot and release boots now start their child again on a fresh port when it dies because another process took the port first.
bun scripts/gate.tsandbun x tsc --noEmitcame back clean.test/kernel-port.test.ts,test/release-boot.test.tsandtest/drill-verdict.test.tspass (22 tests). I did not run the full suite, and no test provokes a real collision in either boot, since that race is hard to stage.What moved
src/kernel/port.tsholdsfreePort(),lostPort(output)andonFreshPort(attempt). It imports nothing, sorelease/boot.tsstill runs when the rest of the tree is broken.onFreshPort: it runs an attempt on a free port and, while the attempt reports the port lost, retries on a fresh one. It makes at most 2 retries, the same budget asapps/process.tsfrom #8686.src/release/boot.ts:bootProofnow callsonFreshPort. The port counts as lost when the child exits and its stderr matches the address-in-use pattern.src/snapshot/boot.ts:bootRestoreddoes the same. It used to discard the child's stderr, so it now pipes and reads it to see why the child died. The failure messages read as before.test/kernel-port.test.tscovers the retry count and the address-in-use pattern.Not done
freePortand the retry loop inapps/process.tsalone, because #8686 settled it andspawn/cell.tsimports it. Moving it onto the shared module would be a follow-up.To try it: run
bun src/release/boot.ts. It boots the tree on a scratch data directory and prints one line.Closes #8691