Skip to content

Read an empty PORT as unset, so SERVER_PORT still moves the server and NaN never reaches Bun.serve - #340

Open
kevin9327 wants to merge 1 commit into
CopilotKit:mainfrom
kevin9327:server-port-empty-string
Open

Read an empty PORT as unset, so SERVER_PORT still moves the server and NaN never reaches Bun.serve#340
kevin9327 wants to merge 1 commit into
CopilotKit:mainfrom
kevin9327:server-port-empty-string

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

What this changes

#312 let either PORT or SERVER_PORT move the server, and refused the two disagreeing. It read them as process.env.PORT ?? process.env.SERVER_PORT ?? "3001", and ?? only fires on undefined. An unset variable declared in a compose file, or PORT= left in a .env next to a SERVER_PORT that was set, arrives as "" — the same thing #96 and #114 found for the computer's PORT and timeouts — so:

  • rawPort was "", SERVER_PORT never consulted;
  • the disagreement check is PORT && SERVER_PORT && …, so it never fired either;
  • Number.parseInt("", 10) is NaN, and Bun.serve({ port: NaN }) does not throw: it binds an ephemeral port.

So the server came up on a port nobody had asked for, and scripts/start.sh, which polls SERVER_PORT, reported a server that never started. That is the failure #312 set out to remove, back through the other name. Separately, parseInt parses a prefix: PORT=30o1 started the server on port 30 without a word.

The port now belongs to loadConfig in server/src/config.ts, read through the same optional every other setting uses (trimmed, empty means unset), refusing anything that is not a whole number in 1–65535 the way the handoff caps and AGENT_STALL_TIMEOUT_MS already refuse a typo, and comparing the two names as numbers. index.ts reads config.port. .env.example says an empty value counts as unset.

Where it runs

  • New state that outlives a request? None. A number read once at boot.
  • What happens on the second replica? Each replica reads its own environment as before; the only change is what an empty value means.
  • Anything serialised? No.
  • Anything fanned out to a browser? No.
  • New listener, port, or schedule? No — this is about the one listener the server has coming up where it was told to.

Boundary and audit

  • Every acting call still goes through the gateway: nothing on that path is touched.
  • New refusals and new failures each write a row: the new refusal is at boot, before there is a database to write to; it is thrown, like every other configuration refusal in config.ts.
  • Nothing new is trusted from the client.

Changelog

  • A section under Unreleased.

Proof

Before, with main's expression and PORT="", SERVER_PORT="3005":

$ bun before.ts
{"rawPort":"","disagreeCheckFires":false,"port":null}   # port is NaN (JSON prints it as null)
Bun.serve bound 62827                                   # an ephemeral port, not 3005, not 3001
'30o1' -> 30

After — loadConfig tests, including the empty-string case (PORT: "" with SERVER_PORT: "3005" → 3005, both empty → 3001), the disagreement, and refusal of 30o1, three, 0, 65536, 1.5, -1 under both names:

$ cd server && bun test tests/config.test.ts
 82 pass
 0 fail

bun run format:check, bun run lint, and bun run typecheck (server) pass. The app typecheck fails on a clean clone until bun install picks up react-use-measure from #317, which is unrelated to this change.

…d NaN never reaches Bun.serve

CopilotKit#312 read the port as `process.env.PORT ?? process.env.SERVER_PORT ?? "3001"`. `??` fires only on
undefined, and a variable a compose file declares but the host never set, or `PORT=` left in a
`.env`, arrives as an empty string: SERVER_PORT was never consulted, the disagreement check
(`PORT && SERVER_PORT && …`) never fired, and `Number.parseInt("")` handed `Bun.serve` a NaN, which
it answers by binding an ephemeral port. The server came up somewhere nobody had asked for, and
the script polling SERVER_PORT reported one that never started — the failure CopilotKit#312 set out to
remove, back through the other name. `parseInt` also parses a prefix, so `PORT=30o1` started the
server on port 30 without a word.

The port is now read by `loadConfig` through the same `optional` every other setting uses, where
empty means unset, refusing anything that is not a whole number in 1–65535 the way the handoff
caps and AGENT_STALL_TIMEOUT_MS already refuse a typo. Tests cover the empty-string case that is
the whole point, the disagreement, and the refusals under both names.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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