-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: pre-flight port check and graceful shutdown to prevent EADDRINUSE #1091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,6 +29,7 @@ import rateLimit from "express-rate-limit"; | |||||||||||||||||||||||||||||||||||||
| import { findActualExecutable } from "spawn-rx"; | ||||||||||||||||||||||||||||||||||||||
| import mcpProxy from "./mcpProxy.js"; | ||||||||||||||||||||||||||||||||||||||
| import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto"; | ||||||||||||||||||||||||||||||||||||||
| import net from "net"; | ||||||||||||||||||||||||||||||||||||||
| import { fileURLToPath } from "url"; | ||||||||||||||||||||||||||||||||||||||
| import { dirname, join } from "path"; | ||||||||||||||||||||||||||||||||||||||
| import { readFileSync } from "fs"; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -822,6 +823,30 @@ const PORT = parseInt( | |||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| const HOST = process.env.HOST || "localhost"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Check port availability before attempting to bind. | ||||||||||||||||||||||||||||||||||||||
| // Prevents confusing EADDRINUSE errors from stale processes. | ||||||||||||||||||||||||||||||||||||||
| function checkPort(targetHost: string, targetPort: number): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve) => { | ||||||||||||||||||||||||||||||||||||||
| const tester = net.createServer(); | ||||||||||||||||||||||||||||||||||||||
| tester.once("error", () => { | ||||||||||||||||||||||||||||||||||||||
| resolve(false); | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| tester.once("listening", () => { | ||||||||||||||||||||||||||||||||||||||
| tester.close(() => resolve(true)); | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| tester.listen(targetPort, targetHost); | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const portFree = await checkPort(HOST, PORT); | ||||||||||||||||||||||||||||||||||||||
| if (!portFree) { | ||||||||||||||||||||||||||||||||||||||
| console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `); | ||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||
| `💡 To fix: run "lsof -ti:${PORT} | xargs kill -9" to free the port, or set SERVER_PORT to use a different port.`, | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+841
to
+846
|
||||||||||||||||||||||||||||||||||||||
| const portFree = await checkPort(HOST, PORT); | |
| if (!portFree) { | |
| console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `); | |
| console.error( | |
| `💡 To fix: run "lsof -ti:${PORT} | xargs kill -9" to free the port, or set SERVER_PORT to use a different port.`, | |
| ); | |
| function getPortInUseRemediation(targetPort: number): string { | |
| if (process.platform === "win32") { | |
| return `💡 To fix: close the process using port ${targetPort} (for example, run "netstat -ano | findstr :${targetPort}" to find the PID, then "taskkill /PID <PID> /F"), or set SERVER_PORT to use a different port.`; | |
| } | |
| return `💡 To fix: close the process using port ${targetPort} (for example, run "lsof -ti:${targetPort} | xargs kill -9"), or set SERVER_PORT to use a different port.`; | |
| } | |
| const portFree = await checkPort(HOST, PORT); | |
| if (!portFree) { | |
| console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `); | |
| console.error(getPortInUseRemediation(PORT)); |
Copilot
AI
Apr 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shutdown() calls server.close() unconditionally. In Node, calling close() when the server isn't listening (or calling it twice if multiple signals fire) can throw ERR_SERVER_NOT_RUNNING. Add a guard (e.g., let shuttingDown = false + if (!server.listening) process.exit(0)) and consider clearing the force-exit timeout when close completes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkPort()currently resolvesfalsefor any listen error, which can misreport other failure modes (e.g.,EACCESfor privileged ports, invalid host) as "PORT IS IN USE". Capture the error and only treatEADDRINUSEas "in use"; for othererr.codevalues either rethrow or surface a more accurate message. Also consider callingtester.close()in the error path to avoid leaving a handle around in edge cases.