-
Notifications
You must be signed in to change notification settings - Fork 131
fix: Robust port conflict handling across CLI, server, and Electron #699
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?
Conversation
Addresses issue #698 by providing user-friendly error messages when the application tries to bind to a port that is already in use. Changes: - Added comprehensive EADDRINUSE error handling in server/index.ts - Enhanced error messages in bin/start.js with helpful commands to find and kill processes - Added error dialog for Electron app in src/main.ts with platform-specific commands - Error messages now include platform-specific commands (Windows vs Unix-like) - Provides clear solutions: stop conflicting process, use different port, or set PORT env var 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
} | ||
// Re-throw other errors | ||
throw error; | ||
} |
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.
Bug: Signal Handlers Fail on Undefined Server
The SIGINT and SIGTERM handlers call server.close()
without checking if server
is defined. If server initialization fails (e.g., due to a port conflict or other serve()
errors), server
remains undefined. If a signal arrives before process.exit(1)
completes, calling server.close()
on an undefined object leads to a runtime error.
WalkthroughThe pull request enhances port-conflict error handling across three application layers. In the startup script, port-conflict messages now display in formatted boxes with solutions and platform-specific commands. The server initialization wraps HTTP server creation in a try-catch block that specifically catches EADDRINUSE errors and logs styled messages before exiting. Similarly, the Electron main process intercepts port-in-use errors, logs user-friendly text, displays a dialog, and throws a new error. The control flow remains unchanged; these modifications layer enhanced user-facing error communication atop existing startup logic. Comment |
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.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/index.ts (1)
418-428
: Guard against undefined server in shutdown handlers.If server creation throws (e.g., port conflict),
server
remains undefined. The SIGINT and SIGTERM handlers will attemptundefined.close()
, causing a runtime error.Apply this diff to add optional chaining:
// Handle graceful shutdown process.on("SIGINT", () => { console.log("\n🛑 Shutting down gracefully..."); - server.close(); + server?.close(); process.exit(0); }); process.on("SIGTERM", () => { console.log("\n🛑 Shutting down gracefully..."); - server.close(); + server?.close(); process.exit(0); });
🧹 Nitpick comments (1)
bin/start.js (1)
512-537
: Extract duplicated port-conflict messaging into a helper function.The explicit-port and default-port conflict blocks share nearly identical logic—both call
logDivider()
, construct a similar message, calllogBox()
, and throw. This duplication increases maintenance burden and risks divergence.Consider refactoring to a reusable helper:
+function logPortConflictAndThrow(port, solutions) { + logDivider(); + const commands = process.platform === "win32" + ? `netstat -ano | findstr :${port}\ntaskkill /PID <PID> /F` + : `lsof -ti:${port}\nkill $(lsof -ti:${port})`; + logBox( + `Port ${port} is already being used by another process.\n\n${solutions}\n\nFind process using this port:\n${commands}`, + "❌ Port Conflict" + ); + logDivider(); + throw new Error(`Port ${requestedPort} is already in use`); +}Then replace both blocks:
} else { logError(`Explicitly requested port ${requestedPort} is not available`); - logDivider(); - logBox( - `Port ${requestedPort} is already being used by another process.\n\nSolutions:\n1. Stop the process using this port\n2. Use a different port with --port <number>\n\nFind process using this port:\n${process.platform === "win32" ? `netstat -ano | findstr :${requestedPort}\ntaskkill /PID <PID> /F` : `lsof -ti:${requestedPort}\nkill $(lsof -ti:${requestedPort})`}`, - "❌ Port Conflict" - ); - logDivider(); - throw new Error(`Port ${requestedPort} is already in use`); + logPortConflictAndThrow( + requestedPort, + "Solutions:\n1. Stop the process using this port\n2. Use a different port with --port <number>" + ); } } else { // Fixed port policy: use default port 3000 and fail fast if unavailable logInfo("No specific port requested, using fixed default port 6274"); if (await isPortAvailable(requestedPort)) { PORT = requestedPort.toString(); logSuccess(`Default port ${requestedPort} is available`); } else { logError( `Default port ${requestedPort} is already in use. Please free the port`, ); - logDivider(); - logBox( - `Port ${requestedPort} is already being used by another process.\n\nSolutions:\n1. Stop the process using this port\n2. Specify a different port with --port <number>\n3. Set PORT environment variable\n\nFind process using this port:\n${process.platform === "win32" ? `netstat -ano | findstr :${requestedPort}\ntaskkill /PID <PID> /F` : `lsof -ti:${requestedPort}\nkill $(lsof -ti:${requestedPort})`}`, - "❌ Port Conflict" - ); - logDivider(); - throw new Error(`Port ${requestedPort} is already in use`); + logPortConflictAndThrow( + requestedPort, + "Solutions:\n1. Stop the process using this port\n2. Specify a different port with --port <number>\n3. Set PORT environment variable" + ); } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
bin/start.js
(2 hunks)server/index.ts
(1 hunks)src/main.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}
: Prefer interfaces for defining object shapes
Constrain generics with appropriate type bounds
Use type guards to narrow unknown or union types before usage
Enforce import ordering consistently
**/*.{ts,tsx}
: Prefer named exports in TypeScript modules
Use 2-space indentation
Declare types and interfaces using PascalCase
Files:
src/main.ts
server/index.ts
src/{main,preload}.ts
📄 CodeRabbit inference engine (AGENTS.md)
Electron entry points are src/main.ts and src/preload.ts
Files:
src/main.ts
server/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.ts
: Implement robust error handling with safe error messages and log sanitization
Set appropriate security headers for backend responses
Apply rate limiting and backoff strategies to protect APIsUse TypeScript for all backend source files
Files:
server/index.ts
server/index.ts
📄 CodeRabbit inference engine (server/CLAUDE.md)
Use server/index.ts as the server entry point
Files:
server/index.ts
🧠 Learnings (1)
📚 Learning: 2025-10-19T23:07:08.970Z
Learnt from: CR
PR: MCPJam/inspector#0
File: server/CLAUDE.md:0-0
Timestamp: 2025-10-19T23:07:08.970Z
Learning: Applies to server/index.ts : Use server/index.ts as the server entry point
Applied to files:
server/index.ts
🧬 Code graph analysis (2)
src/main.ts (2)
evals-cli/src/utils/logger.ts (1)
error
(229-232)bin/start.js (2)
port
(130-130)port
(341-341)
server/index.ts (1)
bin/start.js (2)
port
(130-130)port
(341-341)
🔍 Remote MCP
Additional Context for PR Review
Based on my research, here are the key findings relevant to reviewing this pull request:
Node.js EADDRINUSE Error Handling
The proper way to catch EADDRINUSE errors in Node.js is to check the error.code
property (not errno
in newer versions). The PR's implementation in server/index.ts
correctly checks both error.code === "EADDRINUSE"
and fallback string matching for the error message, which is a robust approach that handles both direct error code checking and variations.
Error Handling Best Practices
Best practices include implementing proper error handling to detect when EADDRINUSE occurs and respond appropriately, which could involve logging the error for investigation or programmatically choosing a different port. The PR goes further by providing actionable user guidance, which exceeds basic best practices.
Platform-Specific Commands Accuracy
The PR includes platform-specific commands to find and terminate conflicting processes:
-
Unix-like systems: Use
lsof
to find the PID listening on the target port; the-t
option outputs only the PID, and-i:
specifies the network port. Use thekill
command with the PID; the-9
signal (SIGKILL) forcefully terminates the process. -
Windows systems: Use
netstat
to find the PID, then use PowerShell'sGet-NetTCPConnection
withStop-Process
to directly find the process owning the specified port and terminate it.
Electron Dialog Error Handling
Electron's dialog API can be called safely before the ready event the app module emits; it is usually used to report errors in early stage of startup. The PR's use of dialog.showErrorBox()
in src/main.ts
is appropriate for port conflict errors during application startup.
Summary for Reviewers
The PR implementation aligns with industry best practices:
- ✅ Proper EADDRINUSE detection using
error.code
property - ✅ Platform-specific commands are accurate (lsof/kill for Unix, netstat/taskkill for Windows)
- ✅ Electron dialog usage is appropriate for startup errors
- ✅ Provides actionable user guidance (three solution options)
- ✅ Maintains graceful error handling with proper logging
[::web_search::]
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: prettier-and-build
port, | ||
hostname: "0.0.0.0", // Bind to all interfaces for Docker | ||
}); | ||
let server: any; |
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.
🛠️ Refactor suggestion | 🟠 Major
Replace any
type with proper server type.
Declaring server
as any
defeats TypeScript's type safety. Let the type be inferred from serve()
or use an explicit type from @hono/node-server
.
As per coding guidelines
Apply this diff:
-let server: any;
+let server: ReturnType<typeof serve> | undefined;
Or allow inference:
-let server: any;
+let server;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let server: any; | |
let server: ReturnType<typeof serve> | undefined; |
🤖 Prompt for AI Agents
In server/index.ts around line 382, the variable "let server: any;" uses the any
type which bypasses TypeScript checks; replace it with a proper server type by
either removing the explicit annotation to allow type inference from the serve()
call (e.g., declare "let server;" or assign immediately) or import and use the
concrete type from @hono/node-server (e.g., add "import type { Server } from
'@hono/node-server'" and change to "let server: Server;"), then ensure
subsequent assignments are compatible with that type.
} catch (error: any) { | ||
// Handle port already in use error | ||
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) { | ||
console.error("\n┌────────────────────────────────────────────────────────────┐"); | ||
console.error("│ ❌ ERROR: Port Already In Use │"); | ||
console.error("├────────────────────────────────────────────────────────────┤"); | ||
console.error(`│ Port ${port} is already being used by another process. │`); | ||
console.error("│ │"); | ||
console.error("│ Solutions: │"); | ||
console.error("│ 1. Stop the other process using this port │"); | ||
console.error("│ 2. Use a different port with --port <number> │"); | ||
console.error("│ 3. Set PORT environment variable │"); | ||
console.error("│ │"); | ||
console.error("│ Find process using this port: │"); | ||
if (process.platform === "win32") { | ||
console.error(`│ netstat -ano | findstr :${port} │`); | ||
console.error(`│ taskkill /PID <PID> /F │`); | ||
} else { | ||
console.error(`│ lsof -ti:${port} │`); | ||
console.error(`│ kill $(lsof -ti:${port}) │`); | ||
} | ||
console.error("└────────────────────────────────────────────────────────────┘\n"); | ||
process.exit(1); | ||
} | ||
// Re-throw other errors | ||
throw error; | ||
} |
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.
🛠️ Refactor suggestion | 🟠 Major
Reuse existing logBox
function instead of manual box drawing.
Lines 392–410 manually construct an error box with console.error
and hard-coded borders, duplicating the logBox
utility already defined at lines 35–59 in this file. Manual formatting risks misalignment and increases maintenance overhead.
Apply this diff to leverage the existing helper:
} catch (error: any) {
// Handle port already in use error
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) {
- console.error("\n┌────────────────────────────────────────────────────────────┐");
- console.error("│ ❌ ERROR: Port Already In Use │");
- console.error("├────────────────────────────────────────────────────────────┤");
- console.error(`│ Port ${port} is already being used by another process. │`);
- console.error("│ │");
- console.error("│ Solutions: │");
- console.error("│ 1. Stop the other process using this port │");
- console.error("│ 2. Use a different port with --port <number> │");
- console.error("│ 3. Set PORT environment variable │");
- console.error("│ │");
- console.error("│ Find process using this port: │");
- if (process.platform === "win32") {
- console.error(`│ netstat -ano | findstr :${port} │`);
- console.error(`│ taskkill /PID <PID> /F │`);
- } else {
- console.error(`│ lsof -ti:${port} │`);
- console.error(`│ kill $(lsof -ti:${port}) │`);
- }
- console.error("└────────────────────────────────────────────────────────────┘\n");
+ const commands = process.platform === "win32"
+ ? `netstat -ano | findstr :${port}\ntaskkill /PID <PID> /F`
+ : `lsof -ti:${port}\nkill $(lsof -ti:${port})`;
+ console.log("\n");
+ logBox(
+ `Port ${port} is already being used by another process.\n\nSolutions:\n1. Stop the other process using this port\n2. Use a different port with --port <number>\n3. Set PORT environment variable\n\nFind process using this port:\n${commands}`,
+ "❌ ERROR: Port Already In Use"
+ );
+ console.log("\n");
process.exit(1);
}
// Re-throw other errors
throw error;
}
Committable suggestion skipped: line range outside the PR's diff.
} catch (error: any) { | ||
// Handle port already in use error with a helpful message | ||
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) { | ||
const errorMessage = `Port ${port} is already in use by another process. Please close the other application and try again.`; | ||
log.error(errorMessage); | ||
|
||
// Show a user-friendly dialog | ||
const { dialog } = require("electron"); | ||
dialog.showErrorBox( | ||
"Port Conflict", | ||
`${errorMessage}\n\nFind and stop the process using port ${port}:\n${process.platform === "win32" ? `netstat -ano | findstr :${port}` : `lsof -ti:${port}`}` | ||
); | ||
throw new Error(errorMessage); | ||
} |
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.
Fix scope error: port
is undefined in the catch block.
Line 91 references port
, but port
is declared inside the try block at line 64 and is not accessible in the catch scope. This will cause a ReferenceError
at runtime when a port conflict occurs.
Apply this diff to move the declaration outside the try block:
async function startHonoServer(): Promise<number> {
+ let port: number;
try {
- const port = app.isPackaged ? 3000 : await findAvailablePort(3000);
+ port = app.isPackaged ? 3000 : await findAvailablePort(3000);
// Set environment variables to tell the server it's running in Electron
process.env.ELECTRON_APP = "true";
Alternatively, if you cannot reliably determine which port failed, avoid referencing it:
} catch (error: any) {
// Handle port already in use error with a helpful message
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) {
- const errorMessage = `Port ${port} is already in use by another process. Please close the other application and try again.`;
+ const errorMessage = `The requested port is already in use by another process. Please close the other application and try again.`;
log.error(errorMessage);
// Show a user-friendly dialog
const { dialog } = require("electron");
dialog.showErrorBox(
"Port Conflict",
- `${errorMessage}\n\nFind and stop the process using port ${port}:\n${process.platform === "win32" ? `netstat -ano | findstr :${port}` : `lsof -ti:${port}`}`
+ `${errorMessage}\n\nUse system tools to find and stop the conflicting process.`
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
} catch (error: any) { | |
// Handle port already in use error with a helpful message | |
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) { | |
const errorMessage = `Port ${port} is already in use by another process. Please close the other application and try again.`; | |
log.error(errorMessage); | |
// Show a user-friendly dialog | |
const { dialog } = require("electron"); | |
dialog.showErrorBox( | |
"Port Conflict", | |
`${errorMessage}\n\nFind and stop the process using port ${port}:\n${process.platform === "win32" ? `netstat -ano | findstr :${port}` : `lsof -ti:${port}`}` | |
); | |
throw new Error(errorMessage); | |
} | |
} catch (error: any) { | |
// Handle port already in use error with a helpful message | |
if (error.code === "EADDRINUSE" || error.message?.includes("address already in use")) { | |
const errorMessage = `The requested port is already in use by another process. Please close the other application and try again.`; | |
log.error(errorMessage); | |
// Show a user-friendly dialog | |
const { dialog } = require("electron"); | |
dialog.showErrorBox( | |
"Port Conflict", | |
`${errorMessage}\n\nUse system tools to find and stop the conflicting process.` | |
); | |
throw new Error(errorMessage); | |
} |
🤖 Prompt for AI Agents
In src/main.ts around lines 88 to 101, the catch block references port which is
declared inside the try and thus is undefined in the catch; either move the port
declaration to an outer scope (declare let port before the try and assign it
inside) so the catch can safely reference it, or stop referencing the local port
in the error message and use a generic message (or extract port from the thrown
error if available) and update the dialog/log to avoid using an undefined
variable.
Summary
Fixes #698 by providing clear, user-friendly error messages when the application fails to start because a port is already in use.
Changes
server/index.ts: Added comprehensive
EADDRINUSE
error handling with a formatted error box showing:bin/start.js: Enhanced error messages for both explicit and default port conflicts with:
--port
flag and environment variable optionssrc/main.ts: Added Electron dialog error handling:
Error Message Examples
CLI Error (Unix-like):
Server Error (Windows):
Test plan
🤖 Generated with Claude Code
Note
Adds robust port-in-use handling across CLI, server, and Electron with clear, formatted guidance and platform-specific commands.
bin/start.js
)logDivider
andlogBox
showing solutions and platform-specific commands.server/index.ts
)serve
in try/catch to detectEADDRINUSE
and print a formatted error box with solutions and Windows/Unix commands; exits with code 1.src/main.ts
)EADDRINUSE
on server start, log a clear message, and show an Electron error dialog with platform-specific steps; rethrows for upstream handling.Written by Cursor Bugbot for commit 49d1ac0. This will update automatically on new commits. Configure here.
📎 Task: https://www.terragonlabs.com/task/b19efca7-9d8e-45d0-ae95-9d3be9d201af