Skip to content

[finding] os start --port forwards the flag on the LOWER-priority channel, so $OS_PORT silently wins — and the banner prints the flag's value while the server binds the other one #12992

Description

@os-litant

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

  1. 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)} : {}),
  2. 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});
  3. 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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions