{"record":{"id":"663cb2585c441a2e","repo":"can1357/oh-my-pi","slug":"port-port-is-in-use-by-holder-image-pid-h","errorCode":null,"errorMessage":"Port ${port} is in use by ${holder.image} (PID ${holder.pid}), which is not identifiable as an omp stats dashboard; refusing to stop it.","messagePattern":"Port (.+?) is in use by (.+?) \\(PID (.+?)\\), which is not identifiable as an omp stats dashboard; refusing to stop it\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stats/src/port-conflict.ts","lineNumber":242,"sourceCode":"\t\tthrow new Error(`Port ${port} is in use, but the listening process could not be identified.`);\n\t}\n\tif (holder.pid === process.pid) {\n\t\tthrow new Error(`Port ${port} is held by the current process (${holder.image}, PID ${holder.pid}).`);\n\t}\n\n\tconst normalizedImage = holder.image\n\t\t.toLowerCase()\n\t\t.replace(/\\.exe$/, \"\")\n\t\t.replace(/ \\(deleted\\)$/, \"\");\n\tconst normalizedCommand = holder.commandLine.toLowerCase().replaceAll(\"\\\\\", \"/\");\n\tconst hasStatsIdentity =\n\t\tnormalizedImage === \"omp-stats\" ||\n\t\t/(?:^|[/\"'\\s])omp-stats(?:\\.exe)?(?:[\"'\\s]|$)/.test(normalizedCommand) ||\n\t\t/\\/packages\\/stats\\/src\\/index\\.ts(?:[\"'\\s]|$)/.test(normalizedCommand) ||\n\t\t(normalizedImage === \"omp\" && /(?:^|\\s)stats(?:\\s|$)/.test(normalizedCommand)) ||\n\t\t/(?:^|\\/)omp(?:\\.exe)?[\"'\\s]+stats(?:[\"'\\s]|$)/.test(normalizedCommand);\n\tif (!STATS_RUNTIME_IMAGES[normalizedImage] || !hasStatsIdentity) {\n\t\tthrow new Error(\n\t\t\t`Port ${port} is in use by ${holder.image} (PID ${holder.pid}), which is not identifiable as an omp stats dashboard; refusing to stop it.`,\n\t\t);\n\t}\n\n\tawait terminatePortHolder(holder);\n\treturn \"retry\";\n}\n\n/**\n * Reuse a secure dashboard or reclaim an insecure HTTP dashboard before binding.\n * The preflight is needed on platforms that permit wildcard and loopback-specific\n * listeners to coexist on one port.\n */\nexport async function prepareStatsPort(port: number, hostname = STATS_DASHBOARD_HOSTNAME): Promise<\"retry\" | \"reuse\"> {\n\tif (port === 0) return \"retry\";\n\tconst probe = await probeStatsDashboard(port, hostname);\n\tif (probe === \"reusable\") return \"reuse\";\n\tif (probe === \"occupied\") return reclaimStatsPort(port);","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/stats/src/port-conflict.ts#L224-L260","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait prepareStatsPort(21590); // throws: holder is 'vite' — not an omp stats dashboard\n// after\ntry {\n  await prepareStatsPort(21590);\n} catch (err) {\n  if (err.message.includes('not identifiable as an omp stats dashboard')) {\n    process.env.OMP_STATS_PORT = '21591'; // move on instead of killing the other app\n    await prepareStatsPort(21591);\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"const holder = await findPortHolder(port);\nif (holder) {\n  console.log(`Port ${port} held by ${holder.image} (PID ${holder.pid}) — verify it is safe to stop or pick another port`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await prepareStatsPort(port);\n} catch (err) {\n  if (err.message.includes('not identifiable as an omp stats dashboard')) {\n    console.error(`Refusing to kill unknown process; use OMP_STATS_PORT or another port: ${err.message}`);\n    process.env.OMP_STATS_PORT = String(port + 1);\n    await prepareStatsPort(port + 1);\n  } else throw err;\n}","preventionTips":["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"],"tags":["port","safety","process-identification","permissions"],"backgroundTag":"port-holder-unrecognized","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}