Repository navigation
fix(cli): os dev shifts off a busy port before it compiles, so the config reads the port the server binds - #26330
Conversation
… pings on the shifted port Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5
…ts the shifted port, so the config reads the port the server binds Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5
… the ping fails fast and the holder releases Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5
…uto-shift note Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ4sTujBVMFFGmdXcRmdd5
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 31 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d61a1d74969fe4823beca10485765b46f0bdfdfd && git checkout d61a1d74969fe4823beca10485765b46f0bdfdfd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 46255b19842480a4f5e4a2556ae9f2e0764c9943 42bd366894ee086ac41f19765db65e53372102e2 && git checkout -B drift-repro 46255b19842480a4f5e4a2556ae9f2e0764c9943 && git merge --no-ff 42bd366894ee086ac41f19765db65e53372102e2
node scripts/docs-audit/affected-docs.mjs --json 46255b19842480a4f5e4a2556ae9f2e0764c9943
|
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Fixes #26270
Clause-②: no
Metadata-protocol: none
What changed
When
os devshifts off a busy port, the config now reads the port the server binds, through the one port channel #26243 set up (portFlagEnv,OS_PORT/PORT). No second copy of the precedence: the requested port is the expressiondev.tsalready had (port ?? '3000'), the walk isserve.ts's owngetAvailablePort, and the notice isserve.ts's own wording, extracted intoformatDevPortShiftNotice.os dev(packages/cli/src/commands/dev.ts). Before the compile (the first process that evaluates the config), the parent runs the same walk from the same requested port. On a shift it prints the shift notice (stderr), exports the bound port withportFlagEnv(so the compile, the watch recompiles and theservechild read it), and forwards it as the child's--port, so the child binds it without walking again and a restart keeps it. An exhausted walk is left to the child, which repeats it and printsformatExhaustedPortSearchNoticeas before.os serve(packages/cli/src/commands/serve.ts). The development auto-shift branch now exports the port it shifted to withportFlagEnv, beforeloadProjectEnvFiles, the config load, and the directos serve --devboot compile (cli/serve:os serve --devwithOS_PORTset still serves the build-time connectorbaseUrlfrom/metawhile dispatch reaches the live port (the #26130 split on the serve door) #26242). That is the same branch whether it was entered through--devorNODE_ENV=development.utils/port-contract.ts:portFlagEnv's caller list names the shift.examples/app-showcase/src/system/self-url.tsis unchanged: its header claims stay true.Measured at the public door (
examples/app-showcase, a TCP listener holding the requested port)main(after #26268)os dev --fresh -p 41310/api/v1/meta/connector/showcase_status_apibaseUrl:41310;POST /api/v1/automation/showcase_declarative_connector_ping/trigger400 FLOW_FAILEDbaseUrl:41331/:41401; trigger 200,ping.body.data.status: "ok"OS_PORT=41320 os dev --fresh(no flag)baseUrl:41320; trigger 400baseUrl:41341; trigger 200os serve --dev -p 41370baseUrl:41391, trigger 200NODE_ENV=development os serve -p 41380The
maincolumn's failure text isconnector_action(showcase_status_api.request) failed: resilientFetch: request timed out after 30000msbecause the probe's holder accepts and never answers; with nothing answering, the card measuredfetch failed. The ruling's scope note onos servewithout--dev: it does shift, underNODE_ENV=developmentonly (portAutoShiftAllowed), and it is the same branch through the same channel, so it is fixed by the same line. Productionos servestill refuses a busy port, unchanged.The pin
packages/cli/test/dev-connector-env-parity.integration.test.tsgains a third boot:os dev -p Nwith N held for the whole boot by an HTTP neighbour answering 503. The bound port is read from the child's own banner. Three arms: the materialized connectorbaseUrl, the served/api/v1/meta/connector/NAMEbaseUrl, and a flow pinging/api/v1/healththrough the connector (fxp_ping, the fixture'sshowcase_declarative_connector_ping) answering 200.expected 'http://127.0.0.1:37609' to be 'http://127.0.0.1:37610'(twice) andFLOW_FAILED … connector_action(fxp_self.ping) failed: health answered 503: expected 400 to be 200. The earlier cli/dev: on anos devboot withOS_PORTset, served connector metadata reads the build-timebaseUrl(http://127.0.0.1:3000) while dispatch reaches the live port, so/metadisagrees with what runs #26130/cli/dev:os dev -p NwithoutOS_PORTleaves the showcase self URL at3000, so its self-ping connector failsfetch failedand/metaserves:3000(the #7538 symptom on the port-flag path) #26243 arms stayed green.serve-dev-connector-env-parity.integration.test.tsafter mergingmain.holdPort()from the shared helper never answers, so itsrelease()waited on the CLOSE_WAIT socket the ping left behind (the hook timed out at 600 s). The file owns an answering neighbour instead, asserve-port-drift-notice.e2e.test.tsdoes; no draw is added (the port is held, not released, so thereleased-port-production-spawnledger is unchanged).Tests and gates (head 42bd366)
pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: lockVERDICT command-exit 0, Test Files 334 passed, Tests 4884 passed, at 42bd366 (4878 before the main merge, at 4cb7cd3).--project integrationon the two parity files: 2 files, 9 tests passed.pnpm --filter @objectstack/cli typecheck(tsc pluscheck:test-typecheck, whose project lists this test file): exit 0.--no-inline-configon the four touched sources: exit 0.dispatch-gates --commands: 69 families, all run,--ranreconciliation69 derived … 68 run, 1 NOT-MEASURED. Every run exited 0 except:check:dual-build-cjs-loadsexit 3 (Run pnpm build first, NOT MEASURED, CI builds and runs it), andcheck-plugin-teardown-shape --self-testexit 3 on the shallow checkout, 0 aftergit fetch origin 621a4876(its pinned fixture).Acceptance notes
/api/v1/meta) keeps the probed port until the next recompile. Stated in the code and the changeset.--porttext notice. When the parent shifts, the child receives the bound port as a number, soserve'sportTextReadNotice(for text like08080read as 8080) does not print on that boot. It still prints whenever no shift happens.holdPort()intest/helpers/serve-process.tscannot be released once something has connected to it (CLOSE_WAIT,server.close()never calls back). Not filed: no product defect, no reach outside tests. Carrier: none.Generated by Claude Code