can1357/oh-my-pi · error · Error
Port ${port} is in use by ${holder.image} (PID ${holder.pid}
Error message
Port ${port} is in use by ${holder.image} (PID ${holder.pid}), which is not identifiable as an omp stats dashboard; refusing to stop it. What it means
reclaimStatsPort() will only terminate a port holder it positively identifies as an omp stats dashboard (image name like omp-stats/omp-stats.exe, an omp stats command line, or the packages/stats dev entrypoint). If either the image is unknown or the command line lacks stats identity, it throws rather than killing an unrelated process. This prevents the stats server from murdering e.g. a database or another user's app that happens to be on the port.
Source
Thrown at packages/stats/src/port-conflict.ts:242
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.`,
);
}
await terminatePortHolder(holder);
return "retry";
}
/**
* Reuse a secure dashboard or reclaim an insecure HTTP dashboard before binding.
* The preflight is needed on platforms that permit wildcard and loopback-specific
* listeners to coexist on one port.
*/
export async function prepareStatsPort(port: number, hostname = STATS_DASHBOARD_HOSTNAME): Promise<"retry" | "reuse"> {
if (port === 0) return "retry";
const probe = await probeStatsDashboard(port, hostname);
if (probe === "reusable") return "reuse";
if (probe === "occupied") return reclaimStatsPort(port);View on GitHub (pinned to 9690622007)
Solutions
- Stop the occupying process manually after verifying what it is (ps -p <pid> -o args)
- Configure the stats server to use a free port instead of reclaiming this one
- If it IS an omp dashboard but renamed, launch it via a recognized entrypoint (omp stats / omp-stats binary) so identity heuristics match
- File/extend the identity heuristics if a legitimate omp runtime image is not recognized
Example fix
// before
await prepareStatsPort(21590); // throws: holder is 'vite' — not an omp stats dashboard
// after
try {
await prepareStatsPort(21590);
} catch (err) {
if (err.message.includes('not identifiable as an omp stats dashboard')) {
process.env.OMP_STATS_PORT = '21591'; // move on instead of killing the other app
await prepareStatsPort(21591);
} else throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
const holder = await findPortHolder(port);
if (holder) {
console.log(`Port ${port} held by ${holder.image} (PID ${holder.pid}) — verify it is safe to stop or pick another port`);
} Try / catch
try {
await prepareStatsPort(port);
} catch (err) {
if (err.message.includes('not identifiable as an omp stats dashboard')) {
console.error(`Refusing to kill unknown process; use OMP_STATS_PORT or another port: ${err.message}`);
process.env.OMP_STATS_PORT = String(port + 1);
await prepareStatsPort(port + 1);
} else throw err;
} Prevention
- Never assume the port holder is disposable — inspect it with ps before manual kills
- Launch the dashboard through recognized entrypoints (omp stats / omp-stats) so identity heuristics match
- Pick a dedicated port for the stats dashboard to avoid collisions with dev servers
- Do not bypass the safety check by killing PIDs blindly in scripts
When it happens
Trigger: prepareStatsPort/recoverStatsPort finds a port occupied by a process whose normalized image is not in STATS_RUNTIME_IMAGES or whose normalized command line fails all hasStatsIdentity heuristics — e.g. nginx, node running some other script, a dev server on the same default port.
Common situations: Another project's dev server bound to the shared default stats port; a renamed/copied omp binary whose image name no longer matches; command-line shaping (args via wrapper script) defeating the identity regex; running the dashboard through a process manager that rewrites argv.
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})
- Port ${port} is in use, but the listening process could not
- {}: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/663cb2585c441a2e.
Report an issue: GitHub.