Skip to content

fix(test): base the dev-server test ports past the fetch bad-ports list - #1461

Merged
vivek7405 merged 1 commit into
mainfrom
fix/test-port-blocklist
Sep 2, 2026
Merged

fix(test): base the dev-server test ports past the fetch bad-ports list#1461
vivek7405 merged 1 commit into
mainfrom
fix/test-port-blocklist

Conversation

@vivek7405

Copy link
Copy Markdown
Collaborator

test/bun/dev-public-before-warm.mjs failed CI on #1460 with a bare [TypeError: fetch failed] whose cause was bad port, naming nothing in the code under test. It is not a race and not flake in the usual sense.

Cause

The port is derived as base + (process.pid % n), and the range was 10000-10255:

const PORT = 10000 + (process.pid % 256);

10080 is the last entry on the WHATWG Fetch bad-ports list, so fetch() rejects it before it opens a socket. No server is involved in the failure at all. Reproduced locally:

10079 -> connects normally
10080 -> bad port
10081 -> connects normally

So a run whose pid is 80 mod 256 fails every request in the file. That is about one run in 256, and it is deterministic given the pid rather than timing-dependent, which is what makes it read as flake and survive a re-run.

dev-morph-verdict.mjs had the same exposure on 9990-10229 and had simply not been unlucky yet. The other three pid-derived ports sit below 10080 and are safe:

test range spanned 10080
dev-reload-retry 9500-9739 no
dev-hot-reload 9700-9949 no
dev-extra-watch 9750-9989 no
dev-morph-verdict 9990-10229 yes, fixed here
dev-public-before-warm 10000-10255 yes, this is the one that fired

Fix

Both are based past 10080: 10100-10355 and 10400-10639.

Scanning 9400-10700 with fetch() confirms 10080 is the only blocked port in the neighbourhood, so any base above it is permanently safe rather than merely lucky.

The new ranges stay disjoint from each other and remain entirely above the 9989 ceiling that the existing comment in dev-public-before-warm.mjs reasons about, so its collision argument is preserved intact. That comment was not wrong; it reasoned carefully about ranges not colliding with each other and simply predated the blocklist constraint. It now records both, so the next person choosing a port has the whole rule in one place.

Verification

Ports and syntax only, no behaviour change to any assertion. node --check passes on both files, and the new ranges are confirmed clear of 10080.

test/bun/dev-public-before-warm.mjs failed CI on #1460 with a bare
[TypeError: fetch failed] whose cause was "bad port", naming nothing in
the code under test.

The port is derived as base + (process.pid % n), and the range was
10000-10255. 10080 is the last entry on the WHATWG Fetch bad-ports list,
so fetch() rejects it before opening a socket and no server is involved
in the failure at all. A run whose pid was 80 mod 256 therefore failed
every request in the file. That is about one run in 256, and it is
deterministic given the pid rather than timing-dependent, which is
exactly what makes it read as flake and survive a re-run.

dev-morph-verdict.mjs had the same exposure on 9990-10229 and had simply
not been unlucky yet.

Both are now based past 10080: 10100-10355 and 10400-10639. Scanning
9400-10700 confirms 10080 is the only blocked port in the neighbourhood,
so any base above it is permanently safe. The ranges stay disjoint from
each other and remain entirely above the 9989 ceiling that the existing
comment in dev-public-before-warm reasons about, so its collision
argument is preserved. That comment was not wrong, it just predated the
blocklist constraint, and it now records both.
@vivek7405
vivek7405 merged commit 9de0c80 into main Sep 2, 2026
10 checks passed
@vivek7405
vivek7405 deleted the fix/test-port-blocklist branch September 2, 2026 19:42
vivek7405 added a commit that referenced this pull request Sep 2, 2026
#1461 re-based two dev-server test ports off 10080, which fixed those two
files and nothing else. The next dev-server test starts from a copy of
whichever neighbour is nearest, so the same range can come back.

The bug is worth guarding as a CLASS rather than as two constants,
because its cost is out of proportion to the typo. It is deterministic
given the pid rather than timing-dependent, so a re-run goes green and it
reads as flake for as long as it survives. And the symptom points nowhere
near the cause: dev-morph-verdict fails as "dev server never came up"
while its own captured log says the server is ready on 10080. The server
is fine, the poll just cannot reach it.

This reads the port declarations out of the source and fails if any range
covers a WHATWG bad port. It reads rather than imports because these
modules spawn a real dev server on import, so evaluating them here would
cost minutes and, at the wrong pid, would be the very failure being
guarded against.

The second assertion is the one that matters most: it fails if the guard
stops finding the declarations at all. Without it the first passes
vacuously the moment the idiom is respelled, which is the same shape as
the og-card fit pass that shipped with a condition that could never be
false. Both assertions were verified against their counterfactuals: the
first fails when a port base is reverted, the second when the pattern is
broken.
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.

1 participant