can1357/oh-my-pi · error · Error
Port ${port} is held by the current process (${holder.image}
Error message
Port ${port} is held by the current process (${holder.image}, PID ${holder.pid}). What it means
reclaimStatsPort() refuses to proceed when the PID occupying the stats port is the current process itself. Killing it would terminate the running omp process, so the code throws with the holder image and PID instead of attempting reclamation. This guards against a self-kill during port recovery.
Source
Thrown at packages/stats/src/port-conflict.ts:227
}
}
try {
process.kill(holder.pid, "SIGKILL");
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ESRCH") return;
throw new Error(`Failed to kill ${holder.image} (PID ${holder.pid})`, { cause: error });
}
await Bun.sleep(PROCESS_EXIT_POLL_MS);
}
async function reclaimStatsPort(port: number): Promise<"retry"> {
const holder = await findPortHolder(port);
if (!holder) {
throw new Error(`Port ${port} is in use, but the listening process could not be identified.`);
}
if (holder.pid === process.pid) {
throw new Error(`Port ${port} is held by the current process (${holder.image}, PID ${holder.pid}).`);
}
const normalizedImage = holder.image
.toLowerCase()
.replace(/\.exe$/, "")
.replace(/ \(deleted\)$/, "");
const normalizedCommand = holder.commandLine.toLowerCase().replaceAll("\\", "/");
const hasStatsIdentity =
normalizedImage === "omp-stats" ||
/(?:^|[/"'\s])omp-stats(?:\.exe)?(?:["'\s]|$)/.test(normalizedCommand) ||
/\/packages\/stats\/src\/index\.ts(?:["'\s]|$)/.test(normalizedCommand) ||
(normalizedImage === "omp" && /(?:^|\s)stats(?:\s|$)/.test(normalizedCommand)) ||
/(?:^|\/)omp(?:\.exe)?["'\s]+stats(?:["'\s]|$)/.test(normalizedCommand);
if (!STATS_RUNTIME_IMAGES[normalizedImage] || !hasStatsIdentity) {
throw new Error(
`Port ${port} is in use by ${holder.image} (PID ${holder.pid}), which is not identifiable as an omp stats dashboard; refusing to stop it.`,
);
}View on GitHub (pinned to 9690622007)
Solutions
- Close the existing in-process listener before attempting reclamation (call the server's stop/close handler)
- Deduplicate startup: keep a singleton guard so the stats server initializes once per process
- Use a different port for the second instance
- If recovery is intentional, skip reclamation and treat 'already bound by self' as success
Example fix
// before
await recoverStatsPort(port); // throws when pid === process.pid
// after
if (server) {
await server.stop(); // release our own listener first
}
await recoverStatsPort(port); Defensive patterns
Strategy: validation
Validate before calling
const holder = await findPortHolder(port);
if (holder?.pid === process.pid) {
console.warn(`Stats port ${port} is already held by this process — reuse the existing server instead of recovering`);
return; // or close the existing listener first
} Type guard
function isSelfHeld(holder: PortHolder | null): boolean {
return holder?.pid === process.pid;
} Try / catch
try {
await recoverStatsPort(port);
} catch (err) {
if (err.message.includes('held by the current process')) {
// the running server already owns the port — treat as healthy, skip recovery
} else throw err;
} Prevention
- Guard stats-server startup with a singleton so it initializes once per process
- Close the existing in-process listener before attempting port reclamation
- Treat 'port held by self' as a healthy state in recovery logic, not a failure
- Avoid HMR/reload paths that re-run server initialization without stopping the old instance
When it happens
Trigger: prepareStatsPort or recoverStatsPort finds port occupied and findPortHolder() reports holder.pid === process.pid — i.e. the omp stats server is trying to reclaim a port it is itself already bound to (double startup within one process, or recovery logic triggered by a health-check loop in the same process).
Common situations: Two stats-server instances accidentally started in the same process (e.g. HMR reload re-running initialization while the old listener is still open); recovery path invoked from within the already-running server; tests binding the same port in-process.
Related errors
- Failed to stop ${holder.image} (PID ${holder.pid})
- Failed to inspect ${holder.image} (PID ${holder.pid})
- Failed to kill ${holder.image} (PID ${holder.pid})
- Overlapping replacements detected; refine pattern to avoid a
- Computed edit range is out of bounds
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/28396868dc4de8af.
Report an issue: GitHub.