Skip to content

fix: never kill the process holding an explicitly requested port#64

Merged
pitimon merged 2 commits into
mainfrom
fix/serve-port-no-kill
Jul 20, 2026
Merged

fix: never kill the process holding an explicitly requested port#64
pitimon merged 2 commits into
mainfrom
fix/serve-port-no-kill

Conversation

@pitimon

@pitimon pitimon commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Closes #63

Summary

  • serve --port N no longer runs lsof -ti tcp:N and SIGTERM/SIGKILLs the PIDs it finds. An occupied explicit port now fails startup and prints the alternative command. Removes ensurePortFree, findPidOnPort, and the node:child_process import.
  • Default (non-explicit) startup is unchanged — it still walks to the next free port.
  • Also drops the probeLoopbackPort preflight that the first draft of this fix introduced. Its guard block duplicated the existing catch in listenOnAvailablePort (same lastError assignment, same throw guard, same onRetry call) and produced identical observable behavior for every input, while adding a probe-close-to-bind TOCTOU window that did not exist before. listenOnce was already the authoritative, race-safe bind check. Net src/commands/serve.js: -50 lines.
  • README: PowerShell equivalents for forcing a port ($env:PORT) and for inspecting what holds one (Get-NetTCPConnection), plus npx --yes @ipv9/tokentracker-cli invocations that work in PowerShell.

Behavior change to be aware of

This is intentional but it is a breaking change for any supervisor that restarts over a still-live instance on a pinned port. Previously the new process killed the old one and took the port; now it exits. A launchd / systemd / Task Scheduler unit pinning an explicit port must fully stop the old process before starting the new one.

Test plan

  • node --test test/serve-port-hint.test.js — 5/5 pass
  • New test asserts an occupied explicit port rejects with EADDRINUSE and that the pre-existing listener is still listening afterward (the actual regression guard)
  • npm run docs:openwiki:check — 0 findings
  • Windows smoke on the test host (user01@10.100.94.171, Node 20.20.2) — pending release

Pre-existing CI failure — not from this PR

npm run ci:local is RED on this branch, with 4 failures in test/rollout-parser.test.js:

parseKiroCliIncremental canonicalizes Bedrock model IDs and re-buckets on fingerprint change
parseKiroCliIncremental keeps newer session-file turns when older ones have migrated to SQLite (mixed-state, turn-granular)
parseKiroCliIncremental retracts no-loop_id session-file entries via session_id tag (Bug-2)
parseKiroCliIncremental retracts orphan session-file contribution when a conversation migrates into SQLite (TASK-007 + D-1)

The identical 4 failures reproduce on main at 4a9aea8. Failing-test names were diffed between this branch and main and are byte-identical, so this PR introduces zero new failures. main is red on its own and needs a separate fix.

Release state

Not released. This touches src/, so per CLAUDE.md it is release-bound and needs the 4-way version lockstep (package.json, both project.yml MARKETING_VERSION entries, TokenTrackerWin.csproj) plus a DMG workflow run. No version bump is in this PR — awaiting the release decision.

`serve --port N` used to run `lsof -ti tcp:N`, SIGTERM every PID it found,
then SIGKILL the survivors. Requesting a busy port silently destroyed
whatever owned it — including unrelated processes that happened to hold it.

Startup now fails on an occupied explicit port and prints the alternative
command instead. Default (non-explicit) startup keeps walking to the next
free port as before.

Drops `ensurePortFree`, `findPidOnPort`, and the `node:child_process`
import. Also drops the `probeLoopbackPort` preflight from the first draft
of this fix: its guard block duplicated the existing `catch` in
`listenOnAvailablePort` and changed no observable behavior, while opening a
probe-close-to-bind window that did not exist before. `listenOnce` was
already the authoritative, race-safe bind check.

README documents the PowerShell equivalents for forcing a port and for
inspecting what holds one.
@pitimon
pitimon merged commit 8f4615c into main Jul 20, 2026
1 check passed
@pitimon
pitimon deleted the fix/serve-port-no-kill branch July 20, 2026 23:00
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.

serve --port kills whatever process holds the requested port

1 participant