Skip to content

fix(scripts): generate:default-highscore starts its own dev server - #97

Merged
mcdope merged 1 commit into
masterfrom
fix/highscore-generator-dev-server
Aug 14, 2026
Merged

fix(scripts): generate:default-highscore starts its own dev server#97
mcdope merged 1 commit into
masterfrom
fix/highscore-generator-dev-server

Conversation

@mcdope

@mcdope mcdope commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Found while regenerating the default highscores: npm run generate:default-highscore cannot run on its own. It imports DEV_SERVER_URL from run-balancing-telemetry.mjs but never starts a server.

planLevels() spins one up for route planning and stops it again, so by the time the bot phase ran there was nothing listening. Every attempt died on ERR_CONNECTION_REFUSED, and the qualify loop reported:

browser appears dead: 3 consecutive fully-crashed batches

— three batches and several minutes after the actual cause, naming the wrong thing entirely. It only ever worked when someone happened to have a dev server already up, which is why it went unnoticed.

This exact assumption has bitten before

DEV_SERVER_URL's own comment records it:

It used to default to :5173 and assume something was already there. That is true at a developer's keyboard and false everywhere else, which is how single-player SSH lanes came to have never worked: a remote invocation got ERR_CONNECTION_REFUSED on every attempt, then wrote its aggregate and exited zero, so it looked like a 2-second success that banked nothing.

The sibling was fixed by calling ensureDevServer per phase. The generator never got that fix.

What changed

  • New exported devServerOptions(label) so both scripts resolve the server contract from one place. Exported as a function rather than the two constants, because CODEENSTEIN_DEV_URL ("use that server, never stop it") and CODEENSTEIN_TELEMETRY_DEV_PORT ("start our own here") only mean the right thing together — taking one and forgetting the other is the bug waiting to happen.
  • The generator now calls ensureDevServer(devServerOptions("highscore")) and navigates to its URL.

Teardown detail worth a look in review

Released via a process.on("exit") handler, not a finally. The qualify loop throws on a dead browser, and main() also process.exit(1)s on a partial set — a finally covers the first and is skipped by the second, so neither alone is enough.

Safe as a synchronous handler because stop() is child.kill (devServer.mjs); an async teardown would silently not run there. It does not cover SIGTERM/Ctrl-C, and the comment says so rather than implying otherwise.

Leaking matters more than it looks: a stale vite on :5199 means the next run finds it already answering, "reuses" it, and quietly plays a build from before whatever change prompted the re-run — which reads as bad data rather than as a leaked process.

Verification

Ran it with no server up, which is the case that failed:

  • ERR_CONNECTION_REFUSED: 12+ → 0
  • [highscore] starting vite on :5199 now appears
  • :5199 free again after the run

2,927 src tests, 532 script tests. The change to run-balancing-telemetry.mjs is purely additive so it doesn't conflict with the open #96.

No highscore data is committed here — the regeneration itself turned out to be churn (the new board scored lower, within the old board's own 6251-point spread), so defaultHighscore.ts is untouched.

It imported DEV_SERVER_URL from run-balancing-telemetry.mjs but never started
anything. planLevels() spins up a server for route planning and stops it
again, so by the time the bot phase ran there was nothing listening: every
attempt died on ERR_CONNECTION_REFUSED and the qualify loop reported "browser
appears dead: 3 consecutive fully-crashed batches" — three batches and
several minutes after the actual cause, naming the wrong thing entirely. It
only worked when someone happened to have a dev server already up, which is
why it went unnoticed.

This is the same assumption DEV_SERVER_URL's own comment records having
already broken single-player SSH lanes for the sibling script, where a remote
invocation got ERR_CONNECTION_REFUSED on every attempt, wrote its aggregate
and exited zero. The sibling was fixed by calling ensureDevServer per phase;
the generator never got that fix.

New exported devServerOptions(label) so both scripts resolve the server
contract from one place. Exported as a function rather than as the two
constants because CODEENSTEIN_DEV_URL ("use that server, never stop it") and
CODEENSTEIN_TELEMETRY_DEV_PORT ("start our own here") only mean the right
thing together.

Released via a process "exit" handler rather than a finally: the qualify loop
throws, and main() also process.exit(1)s on a partial set, so a finally would
cover the first and be skipped by the second. Safe as a sync handler because
stop() is child.kill. Does not cover SIGTERM, and the comment says so rather
than implying otherwise.

Verified with no server running, which is the case that failed:
ERR_CONNECTION_REFUSED went 12+ -> 0, "[highscore] starting vite on :5199"
now appears, and :5199 is free again afterwards. Additive to
run-balancing-telemetry.mjs so it does not conflict with the open #96.
@mcdope
mcdope merged commit 8f68ec9 into master Aug 14, 2026
20 checks passed
@mcdope
mcdope deleted the fix/highscore-generator-dev-server branch August 14, 2026 21:08
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