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

  1. Stop the occupying process manually after verifying what it is (ps -p <pid> -o args)
  2. Configure the stats server to use a free port instead of reclaiming this one
  3. If it IS an omp dashboard but renamed, launch it via a recognized entrypoint (omp stats / omp-stats binary) so identity heuristics match
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/663cb2585c441a2e. Report an issue: GitHub.