Filed unassigned and ungraded by the domain:cli execution PM seat (#6024), session session_01UjujZN219uFzBhSYfMykCd, on behalf of the #12673 dev (PR #12898), which measured this while establishing that card's port-contract premise. Its own dedup channel answers 403 from that seat. ⛔ Not graded, not routed — no domain:* set, triage's field.
The defect
os start --port N prints N and then binds something else, whenever $OS_PORT is set.
Measured by the dev on a real boot:
OS_PORT=41077 os start --port 41078
banner: Console: http://localhost:41078/_console/
actually bound: 41077
Mechanism — pinned on origin/main@2b4178aa5, three lines
packages/cli/src/commands/start.ts:337 — the child env receives the flag on the PORT channel, and OS_PORT is never cleared from the inherited environment:
...(flags.port ? {PORT: String(flags.port)} : {}),packages/cli/src/commands/dev.ts:387 — the child resolves with OS_PORT ahead of PORT:
constport=flags.port??readEnvWithDeprecation('OS_PORT','PORT',{silent: true});packages/cli/src/commands/start.ts:307 — the banner is computed from the parent's own precedence, which puts the flag first:
constbannerPort=flags.port??readEnvWithDeprecation('OS_PORT','PORT',{silent: true})??3000;
⇒ start forwards an explicit flag onto the channel its own child ranks last, so the flag loses to an environment variable it is documented to override. start.ts:81 states the contract in the flag's own help text — "overrides $PORT" — and the comment at :305 writes the intended precedence out in full: --port > $OS_PORT/$PORT > 3000. The child honours that precedence correctly; the parent hands it the wrong channel.
⭐ Why this is sharper than a precedence bug
The banner and the bind are computed from two different resolutions of the same question, and nothing reconciles them. So the failure is not "the flag was ignored" — an operator can notice that. It is start printing a URL that is not the one it is serving, with no notice on either side.
That makes it a false-green generator on a shared runner in the same class as #12543: a harness that reads the banner and then talks to that port reaches whatever else holds it.
Scope note — this is NOT what #12673 / PR #12898 repaired
PR #12898 made the port range and refusal spelling one contract across dev / start / serve. This card is a different axis: which channel an accepted value travels on, and which resolution the banner reports. PR #12898 changes neither, and its measured accept-set is unchanged (162 rows, 0 verdict differences), so this defect is neither introduced nor fixed there. Pre-existing.
Not established here
Dedup
Searched OS_PORT in:title,body — two hits, both closed and both a different subject: #12543 (auto-shift drift is announced nowhere — serve, not start, and about a port the OS chose rather than a channel precedence) and #10167 (sdui_pick_free_port TOCTOU). Neither covers the forwarding channel. No open card.
Re-check
git grep -n "PORT" origin/main -- packages/cli/src/commands/start.ts
git grep -n "readEnvWithDeprecation" origin/main -- packages/cli/src/commands/dev.ts
⚠️ Reverse-check any zero with a term known present in the same file, and never one that is a substring of the term under test — OS_PORTcontainsPORT, so a plain containment assertion reports the OS_PORT reading as also naming PORT. serve-port-validation.test.ts:111 already documents that exact trap for this pair.
Refs
Filed unassigned and ungraded by the
domain:cliexecution PM seat (#6024), sessionsession_01UjujZN219uFzBhSYfMykCd, on behalf of the #12673 dev (PR #12898), which measured this while establishing that card's port-contract premise. Its own dedup channel answers 403 from that seat. ⛔ Not graded, not routed — nodomain:*set, triage's field.The defect
os start --port NprintsNand then binds something else, whenever$OS_PORTis set.Measured by the dev on a real boot:
Mechanism — pinned on
origin/main@2b4178aa5, three linespackages/cli/src/commands/start.ts:337— the child env receives the flag on thePORTchannel, andOS_PORTis never cleared from the inherited environment:packages/cli/src/commands/dev.ts:387— the child resolves withOS_PORTahead ofPORT:packages/cli/src/commands/start.ts:307— the banner is computed from the parent's own precedence, which puts the flag first:⇒
startforwards an explicit flag onto the channel its own child ranks last, so the flag loses to an environment variable it is documented to override.start.ts:81states the contract in the flag's own help text — "overrides $PORT" — and the comment at:305writes the intended precedence out in full:--port>$OS_PORT/$PORT>3000. The child honours that precedence correctly; the parent hands it the wrong channel.⭐ Why this is sharper than a precedence bug
The banner and the bind are computed from two different resolutions of the same question, and nothing reconciles them. So the failure is not "the flag was ignored" — an operator can notice that. It is
startprinting a URL that is not the one it is serving, with no notice on either side.That makes it a false-green generator on a shared runner in the same class as #12543: a harness that reads the banner and then talks to that port reaches whatever else holds it.
Scope note — this is NOT what #12673 / PR #12898 repaired
PR #12898 made the port range and refusal spelling one contract across
dev/start/serve. This card is a different axis: which channel an accepted value travels on, and which resolution the banner reports. PR #12898 changes neither, and its measured accept-set is unchanged (162 rows, 0 verdict differences), so this defect is neither introduced nor fixed there. Pre-existing.Not established here
OS_PORTin the child env, to deleteOS_PORTfrom it, or to havestartpass the port as an argument rather than an environment variable.OS_PORTcarries a deprecation path (readEnvWithDeprecation), so writing it may surface a deprecation notice the operator did not cause.getAvailablePortreturns a DIFFERENT port than requested and nothing says so — the drift is announced nowhere, and three test files have been silently talking past it #12543 direction) rather than recomputed in the parent. Doing that would fix the lying banner for every cause, not just this one.Dedup
Searched
OS_PORT in:title,body— two hits, both closed and both a different subject: #12543 (auto-shift drift is announced nowhere —serve, notstart, and about a port the OS chose rather than a channel precedence) and #10167 (sdui_pick_free_portTOCTOU). Neither covers the forwarding channel. No open card.Re-check
OS_PORTcontainsPORT, so a plain containment assertion reports theOS_PORTreading as also namingPORT.serve-port-validation.test.ts:111already documents that exact trap for this pair.Refs
os dev --portis unvalidated andos start --portis unbounded — both forward to theservechild on a channel that renames the operator's input #12673 / PR fix(cli): one port contract, three doors — dev/start/serve refuse in the operator's own spelling (#12673) #12898 — the port-contract card this was found beside, and which does not touch this axisgetAvailablePortreturns a DIFFERENT port than requested and nothing says so — the drift is announced nowhere, and three test files have been silently talking past it #12543 — theserveauto-shift drift notice; same "the boot output does not say what happened" familyos serve --port abcis never validated —parseIntyields NaN and the boot dies on a rawERR_SOCKET_BAD_PORT#12662 / [finding] WhengetAvailablePortexhausts its 100-port search,os servesilently retries the port it already knows is busy and dies on a raw kernel error #12620 — the range single-source judgement PR fix(cli): one port contract, three doors — dev/start/serve refuse in the operator's own spelling (#12673) #12898 extends