import net from "node:net"; import { danger, info, shouldLogVerbose, warn } from "../globals.js"; import { logDebug } from "../logger.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { formatPortDiagnostics } from "./ports-format.js"; import { inspectPortUsage } from "./ports-inspect.js"; import type { PortListener, PortListenerKind, PortUsage, PortUsageStatus } from "./ports-types.js"; class PortInUseError extends Error { port: number; details?: string; constructor(port: number, details?: string) { super(`Port ${port} is already in use.`); this.name = "PortInUseError"; this.port = port; this.details = details; } } function isErrno(err: unknown): err is NodeJS.ErrnoException { return Boolean(err && typeof err === "object" && "code" in err); } export async function describePortOwner(port: number): Promise { const diagnostics = await inspectPortUsage(port); if (diagnostics.listeners.length === 0) return undefined; return formatPortDiagnostics(diagnostics).join("\n"); } export async function ensurePortAvailable(port: number): Promise { // Detect EADDRINUSE early with a friendly message. try { await new Promise((resolve, reject) => { const tester = net .createServer() .once("error", (err) => reject(err)) .once("listening", () => { tester.close(() => resolve()); }) .listen(port); }); } catch (err) { if (isErrno(err) && err.code === "EADDRINUSE") { const details = await describePortOwner(port); throw new PortInUseError(port, details); } throw err; } } export async function handlePortError( err: unknown, port: number, context: string, runtime: RuntimeEnv = defaultRuntime, ): Promise { // Uniform messaging for EADDRINUSE with optional owner details. if (err instanceof PortInUseError || (isErrno(err) && err.code === "EADDRINUSE")) { const details = err instanceof PortInUseError ? err.details : await describePortOwner(port); runtime.error(danger(`${context} failed: port ${port} is already in use.`)); if (details) { runtime.error(info("Port listener details:")); runtime.error(details); if (/clawdbot|src\/index\.ts|dist\/index\.js/.test(details)) { runtime.error( warn( "It looks like another clawdbot instance is already running. Stop it or pick a different port.", ), ); } } runtime.error( info("Resolve by stopping the process using the port or passing --port ."), ); runtime.exit(1); } runtime.error(danger(`${context} failed: ${String(err)}`)); if (shouldLogVerbose()) { const stdout = (err as { stdout?: string })?.stdout; const stderr = (err as { stderr?: string })?.stderr; if (stdout?.trim()) logDebug(`stdout: ${stdout.trim()}`); if (stderr?.trim()) logDebug(`stderr: ${stderr.trim()}`); } return runtime.exit(1); } export { PortInUseError }; export type { PortListener, PortListenerKind, PortUsage, PortUsageStatus }; export { buildPortHints, classifyPortListener, formatPortDiagnostics } from "./ports-format.js"; export { inspectPortUsage } from "./ports-inspect.js";