Repository navigation
fix(server): start background tasks only after listen succeeds; health and static-file fixes - #3
Merged
Conversation
serve retries the next port on EADDRINUSE/EACCES, but startServer started its side effects before listen resolved: scanner update listeners, the Claude/Cursor/ZCode/balance pollers, the backup timers, the leaderboard upload and the price refresh. Each failed attempt left a full copy running: duplicate official-API polling, duplicate backups, and a second leaderboard upload that the Worker throttles with 429, which then shows up as 上次上报失败 in the panel. They now start once, in the listen callback. Also: - computeHealth: a parse error outranks 'empty', so a source whose first scan fails (the upstream-format-change case) reports error and doctor stops passing it. - serveFile: a read-stream error (file replaced between stat and open, e.g. during npm update -g) destroys the response instead of leaving the request hanging. Tests: test/server-lifecycle.mjs (16 checks, run in a temp-HOME offline child). 12 fail against the current server.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
token-watcher servewithout--portretries the next port onEADDRINUSE/EACCES(bin/tokenwatcher.js:282-300).startServerstarted all of its side effects beforelistenresolved:Every failed attempt therefore left a full copy running. One busy port means:
last_error, so the panel shows "上次上报失败" even though the first upload succeeded.This was listed as a P2 in
docs/RELEASE-READINESS-2026-09-24.mdand is still present onmain.Change
startServer: side effects move intostartLifecycle(), which runs inside thelistencallback. The signature and callers are unchanged.computeHealth: a parse error now outranks "empty". A source whose first scan fails is exactly the upstream-format-change caseCONTRIBUTING.mdwarns about. It used to show asempty, anddoctorpassed it. Also a 2026-09-24 P2.serveFile: a read-streamerror(file replaced betweenstatandopen, e.g. duringnpm update -g) now destroys the response. Before,pipedidn't forward the error, so the request hung, and the stream emitted an unhandlederrorevent.Tests
test/server-lifecycle.mjsadds 16 checks, run in a child process with a temp HOME andTOKENMETER_OFFLINE=1, so they never touch a real~/.tokenmeter.binpasses onlybalancePoller, and the Claude/Cursor/ZCode pollers are built insidestartServer. The test checks this case and counts one-off timers too: backup at 30 s, first upload at 90 s.serveFilehang.12 of these checks fail against the current
src/server.js. Fullnpm testpasses on Node 22.22.The four PRs in this set are independent. Each adds one registration line after
stall-guard.mjsintest/run.mjs, so the second one merged will need a one-line rebase.🤖 Generated with Claude Code