Repository navigation
fix(server): report the bound port; bind the port-sharing tests to ephemeral ports (#440, 1/2) - #556
Merged
Merged
Conversation
… usable `port: 0` already bound an ephemeral port at the socket level, but `this.port` stayed 0: the banner printed `:0`, CODEMAN_API_URL pointed panes at `:0`, the docker bridge listener and the unauthenticated-bind warnings read 0, and the route context's `port` was a by-value snapshot taken in setupRoutes(), before listen() runs, so `tunnelManager.start(ctx.port, …)` would have been handed 0. - After `app.listen()`, `this.port` takes the number from `this.app.server.address()` (string/null addresses are left alone). - The route context exposes `port` as a getter, and the cron routes get only the `cron` their CronPort declares instead of a spread copy of the context. - `get boundPort()`: the readonly accessor tests use instead of a private field. test/webserver-bound-port.test.ts compares each reader against the socket's own `address().port`; all three tests fail with only the write-back removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…w fixed ports
Four ports were shared by two files each — 3162 (qr-auth / auth-security), 3170
(multiuser-auth / routes/ws-routes), 3230 and 3231 (cod54-hook-event-auth /
routes/voice-routes). Files run serially (`fileParallelism: false`), so the pairs
never met inside one run; they collide between two runs on one host, or with
anything else holding the port. All six files now bind port 0 and read the
number back (`boundPort` for WebServer, `server.address()` after each listen for
the raw Fastify / ws servers).
test/test-ports-guard.test.ts fails on a WebServer built under test/ whose port
argument is not the literal 0 — `new WebServer(…)`, a subclass, or a destructured
alias (`{ WebServer: T }`, as quick-start.test.ts does) — outside a legacy list of
the 43 files that construct one with a non-zero port today; the follow-up sweep
converts them. A converted file cannot stay listed. What it does not cover (helper
parameters, `import { WebServer as X }`, raw listen sites) is written down in it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N
pushed a commit
that referenced
this pull request
Oct 9, 2026
Move test/sse-tile-grid-filter.test.ts to an ephemeral port. The release added it with #561 on fixed port 3287, after #556 was cut, so it is not on the guard's LEGACY_FIXED_PORT_FILES and test/test-ports-guard.test.ts failed on the merged tree. It now builds new WebServer(0, ...) and its url() helper reads server.boundPort (only ever called inside tests, after beforeAll). Converting it is preferred over listing it, since the legacy list is shrink-only. Update the five docs that still told contributors to pick a unique fixed port, which the new guard now rejects for any WebServer test: CLAUDE.md (Adding Features and Testing), AGENTS.md, .github/CONTRIBUTING.md and the wiki's Contributing page (mirrored to the public GitHub wiki). They now say to bind port 0 and read boundPort (or address().port for a raw server), and note that the mobile suite keeps its fixed ports for now, because test/mobile/helpers/server.ts caches servers by port, so createTestServer(0) from two callers would share one server. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N
pushed a commit
that referenced
this pull request
Oct 9, 2026
The Run dropdown scroll browser test built WebServer on the fixed port 3290, which the static port guard (test/test-ports-guard.test.ts, from #556) rejects for any file outside its shrink-only legacy list, so the CI gate failed on the landing branch. The test now binds an ephemeral port with new WebServer(0, ...) and navigates to server.boundPort, and its header records the new convention. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N
pushed a commit
that referenced
this pull request
Oct 9, 2026
Thirteen contributor PRs, each re-checked against its GitHub head, merged with its own merge commit and landing fixes, reviewed, and gated together (524 test files, 10194 tests): #559, #552, #550, #556, #551, #546, #542, #540, #555, #543, #541, #502, #432. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner
|
Merged in 1.40.0, thanks @Randalix! |
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of 2 for #440, split the way you asked: the server change, the conversions for the four shared ports, and the guard. The mechanical sweep of the rest follows as its own PR once this shape has been reviewed.
Server (
fix(server): report the port actually bound)await this.app.listen(...),this.porttakes the number fromthis.app.server.address(). A string address (a pipe) ornullleaves it alone, no cast. Everything that readsthis.portafter listen now gets the real number: the banner (it printed:0),process.env.CODEMAN_API_URL, the docker bridge listener and the unauthenticated-bind warnings.portis a getter. You were right about the ordering:createRouteContext()runs fromsetupRoutes()beforelisten(), so the by-valueport: this.portwould have handedtunnelManager.start()a 0. One place copied the context by spreading it,registerCronRoutes(this.app, { ...ctx, cron }), which would have frozenportat 0 again. Nothing read it there (CronPortonly declarescron), so it now gets{ cron: this.cronService }. No route module destructuresportfrom the context.get boundPort()onWebServer, readonly, for tests.test/webserver-bound-port.test.tsbuilds the context beforestart(), likesetupRoutes()does, and checksboundPort,ctx.portandCODEMAN_API_URLeach against the socket's ownaddress().port. With only the write-back line removed, all three fail.Conversions
The four shared ports, both files of each:
qr-auth/auth-security(3162),multiuser-auth/routes/ws-routes(3170),cod54-hook-event-auth/routes/voice-routes(3230, 3231). Every server in those six files now binds 0, including the other fixed ports they used (3160, 3161, 3163, 3171, 3220, 3221, 3232). The WebServer ones readboundPort. The raw Fastify andwsservers in the two route tests readserver.address()after each listen, so a re-listen inbeforeEachnever reuses a stale number.Two corrections to my issue. First, these pairs were not a race inside one run: both vitest configs set
fileParallelism: false, so files run one after another. They collide between two runs on the same host (that's how I hit them, two worktrees) or with anything else on the port. Second,docker-self-update.test.tsnever bound 3000. My scan counted theCODEMAN_PORT=3000string in an env-parsing fixture, and nothing in the test tree binds 3000 on currentmaster.Guard
test/test-ports-guard.test.tsreads the test tree and fails on a WebServer whose port argument is anything but the literal0. It recognisesnew WebServer(…), a class declaredextends WebServer, and a destructured alias, becausequick-start.test.tsgets its constructor asconst { WebServer: TestWebServer } = await webServerModule, which a plainnew WebServer(match misses. The 43 files that construct a WebServer with a non-zero port today are an explicit legacy list (four of them never callstart(), so they don't actually bind). An entry whose file no longer matches fails, so PR 2 has to remove each name as it converts the file. I checked that a new file withnew WebServer(3999, …)turns the guard red, with the file name and the fix in the message.What it doesn't catch, written down in the file as well: a helper that takes the port as a parameter is checked at the helper, not its callers.
test/mobile/helpers/server.tsis on the list, so the nine mobile tests callingcreateTestServer(PORT)aren't checked. It also missesimport { WebServer as X }andnew mod.WebServer(…), and the rawapp.listen({ port: N })/new WebSocketServer({ port: N })sites, which belong to the sweep.Verification
npm test: 9128 passed, 12 skipped, 4 failed, the same four that fail on a cleanmastercheckout here and pass underLC_ALL=C(the box runsde_DE.UTF-8:test/git-clone.test.tsand two cases intest/routes/case-clone-routes.test.tsclassify git's stderr, which comes back in German, andtest/custom-model-run-menu-ui.test.tsexpects16,384wheretoLocaleString()gives16.384).typecheck,lint,format:checkandcheck:frontend-syntaxare green.No changeset.
🤖 Generated with Claude Code