Uh oh!
There was an error while loading. Please reload this page.
test(spec): let the sdui collision harness own the port it calls busy - #10456
Conversation
`BUSY_HELD=no` / `BUSY_PORT=5180` / `PICKED_WITH_BUSY=5181` reddened three
unrelated PRs in one afternoon. The picker was right every time; the harness
could not guarantee its own precondition.
The occupier took its port from `sdui_pick_free_port` and bound it afterwards.
A reservation is not a bind, so the port stays takeable in that gap — and
`port_held`, spawned milliseconds later, binds and closes that very port.
Measured over 80 trials on an idle container: the probe won the bind 39 times,
and once the occupier's bind landed inside the probe's hold window and the
occupier exited. No external holder of 5180 is required, which is why none was
ever identified.
Both occupiers (case 2 and case 7) now bind `:0` and report back the port the
kernel gave them, so the port is held before it is named.
Second, independent half: sourcing `gen-sdui-manifest.sh` (`set -euo pipefail`
at its top) turned errexit back ON in the harness, overriding its own
`set -uo pipefail`. The failing cleanup `kill` of a dead occupier then aborted
the harness mid-measurement, `execFileSync` threw in the `describe` body, and
vitest reported `0 test` against a bare "Command failed". `set +e` after the
source and a final `exit 0` make a lost precondition fail as an assertion, with
the whole `seen` map printed beside it.
`expect(seen.BUSY_HELD).toBe('yes')` is untouched, both probes still address
127.0.0.1, and case 7 still covers a holder foreign to the registry. A third
sequential pick (RPORT) replaces BUSY_PORT in the "distinct picks in one run"
set, which would otherwise compare an ephemeral port against the 5180 band and
pass for free.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang
commented
Aug 21, 2026
|
os-zhuang
commented
Aug 21, 2026
⛔ Correction to my own note above — withdraw the "new failure line" reading. Nothing to look at before merging.I asked for a look before this merges, on the theory that Traced it rather than leaving it as a hypothesis:
⇒ Those three ✗ lines are expected stderr from the neighbour-refusal legs, which exist to prove a neighbouring run's server answering on our port is refused rather than accepted. They appear in the log because the bot's extraction interleaves stderr; they are not a symptom. The actual failure in run 32430822520 is the same one this PR fixes — the harness dying with a bare What stands from that note: #10466 was evicted at 00:11Z by this test with its own head green at 32/32 and re-queued once, and this PR remains queued-and-unmerged so every queue batch still runs the unfixed test. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
The "held from outside the registry" case staged its holder by picking a port, deleting the claim, then binding it. Between the `rm -f` and the stub's `listen` the port was claimed by nobody, so any concurrent `smoke_pick_free_port` caller scanning from 3210 could legitimately take it — and losing there fails as `STEAL_HELD != THIEF`, which reads as an accusation of the picker on a PR that never touched it. Port the shape PR #10456 landed for the sdui sibling: the stub binds `:0` and reports back the port the kernel gave it, so the port it holds is one it provably owns, and the pick-then-`rm` dance goes away entirely. The registry property gets plainer rather than weaker — a port bound straight from the ephemeral range was never claimed in any registry, so there is no claim to remove. The stub takes NO host argument, unlike the sdui sibling's `127.0.0.1`: `smoke_pick_free_port`'s probe binds the wildcard address because that is the spelling serve.ts's own isPortAvailable() uses. That asymmetry is load-bearing and recorded on #10261; it is preserved here, not converged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B4h3medzvhB9rpfoja9jcw
Fixes: #10370
What actually broke
BUSY_HELD=no/BUSY_PORT=5180/PICKED_WITH_BUSY=5181, byte-identical on threeunrelated PRs in one afternoon (#10365 17:00Z, #10396 19:03Z — evicted from the merge
queue — #10441 19:22Z).
PICKED_WITH_BUSY=5181is the picker answering correctly.BUSY_HELD=noalone is the failure.The card's stated mechanism is not what happens, and the correction matters. The card
says the harness "binds the fixed base port 5180". It does not — it calls
sdui_pick_free_port 5180and binds whatever that returns.BUSY_PORT=5180thereforemeans the picker probed 5180 and found it free. An external holder of 5180 cannot
produce this signature at all: the picker would have skipped it and
BUSY_PORTwould read5181. The port was taken in the window between the pick returning and the occupier
binding — a reservation is not a bind.
The thief is this file.
port_heldbinds-and-closes the very port the occupier isabout to take, and it is spawned milliseconds later. Measured, 80 trials on an idle
container (
race-probe, occupier + first probe launched exactly as the harness launchesthem):
Nothing outside this file has to hold 5180 for that to fire, which answers the card's
open question — "nobody has identified what holds 5180". Nothing did.
Why the failure arrived as
0 testand a bareCommand failedSecond, independent defect, and the PM's structural note was right about the effect but
not the cause. It is not "no
set -e, so the exit status is the last command's":scripts/gen-sdui-manifest.shopens withset -euo pipefail(:24), so sourcing itturns errexit back on, silently overriding the
set -uo pipefailthe harness writes oneline earlier — and contradicting that line's own comment. Once the occupier had exited,
the cleanup
killreturned 1 and errexit ended the harness on that line. Reproduced byforcing the occupier to lose its bind,
bash -x:That is why the captured output stops after
PICKED_WITH_BUSYand whyexecFileSyncthrew in the
describebody.The fix
:0and report back the port the kernel gave them (case 2'sBUSY, case 7'sSPORT). The number is printed from inside thelisteningcallback,so by the time the harness can read it the socket is already held — the property
BUSY_HELDasserts, with no gap left to race through. This is the card's shape 1;shapes 2 and 3 were not needed. Shape 2 was additionally the one constraint 3 warns
about, and shape 3 only shrinks a window that shape 1 removes.
set +eafter thesource, restoring the mode the harness declares for itself,and a final
exit 0: the harness's exit status is not a measurement — everymeasurement is a printed
KEY=VALUEand the assertions grade those. The siblingpublish-smoke-port-collision.test.tsalready does exactly this (set +e +o pipefail,:135) for exactly this reason; this file was the one that had not caught up. It keepspipefail, which is what its ownset -uo pipefailasked for.Evidence
A/B on the same rig. Widening the measured racer —
port_heldholds its socket300 ms before closing, nothing else changed — six runs each:
BUSY_HELD=no(byte-identical to CI), oneSTEAL_HELD=noBUSY_HELD=yesandSTEAL_HELD=yesevery runPositive control on the reject side — the guard still rejects. Ablation: make the
occupier bind, close, then report, i.e. name a port it does not hold. Not a zero-hit:
Note
PICKED_WITH_BUSYequalsBUSY_PORTthere: without the guard this reads as "thepicker ignores busy ports", which is precisely the misdiagnosis the file's header warns
about. The guard fires first and names the real problem.
The same ablation against the pre-fix file, for the reporting shape the second half
replaces — the frames match the card's capture exactly:
No rebuild is involved in either leg: this test resolves nothing through a package
exports/dist— it importsvitestand node builtins only, and sources the shell scriptby absolute path. Both ablations were confirmed on disk by grepping for the injected and
the removed spelling, and the restore leg was verified clean against
HEADand re-rungreen.
Constraints, each checked
expect(seen.BUSY_HELD, JSON.stringify(seen)).toBe('yes')is untouched, byte forbyte (now
:376). Its siblingSTEAL_HELDtoo. The fix makes the preconditionsatisfiable, never optional.
127.0.0.1;nothing wildcard was introduced. Two port-reservation registries now run the same protocol in different directories — converge
sdui_pick_free_portandsmoke_pick_free_portonto one helper #10261 is untouched.a port bound directly from the ephemeral range was never claimed in any registry. The
private
RESVstays, now justified by what it actually buys (claim files this leg alonewrites) rather than by the shared registry knowing the port, which is no longer true.
gives each pick within one run its own portread[BUSY_PORT, NPORT, OPORT]. WithBUSY_PORTephemeral itwould have compared one ephemeral port against the 5180 band and passed for free, so a
third sequential pick from the shared base (
RPORT) replaces it. Three picker outputs,one base, one run — the property is preserved at full strength, not quietly dropped to
two.
Changeset
None owed, re-derived rather than assumed.
@objectstack/specpublishesfiles: ["dist", "json-schema", "liveness", "prompts", "llms.txt", "README.md", "src/**/*.zod.ts", "CHANGELOG.md", "api-surface", "spec-changes.json"]—scripts/is notamong them, so
packages/spec/scripts/gen-sdui-manifest-collision.test.tsreaches nopublished surface.
skip-changesetapplied.Gates
Derived with
node scripts/pm/dispatch-gates.mjsagainst the real diff (it took the changeset from the merge base itself), at
38b1f88:Two narrowings, declared:
check:dev-prereqsandcheck:type-check-debt --re-measureboth demand a fully built 67-package workspace.check:dev-prereqsreds onthat alone here (
"The workspace is not built — 1 unmet precondition"), unrelated to thisdiff;
check:type-check-debt's per-package ratchet is the same ledgercheck:test-typecheckjust reported unchanged above. CI runs both against a built tree.Lint & Repo Gatesmay be red for #10122 (packages/spec/src/migrations/registry.ts,@typescript-eslint/parserMaximum call stack size exceeded). Assigned elsewhere, notthis PR's.
Generated by Claude Code