{"record":{"id":"2370a92fdc4b70be","repo":"can1357/oh-my-pi","slug":"failed-to-kill-holder-image-pid-holder-pid","errorCode":null,"errorMessage":"Failed to kill ${holder.image} (PID ${holder.pid})","messagePattern":"Failed to kill (.+?) \\(PID (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stats/src/port-conflict.ts","lineNumber":216,"sourceCode":"\t\tif (error instanceof Error && \"code\" in error && error.code === \"ESRCH\") return;\n\t\tthrow new Error(`Failed to stop ${holder.image} (PID ${holder.pid})`, { cause: error });\n\t}\n\n\tfor (let attempt = 0; attempt < PROCESS_EXIT_POLLS; attempt++) {\n\t\tawait Bun.sleep(PROCESS_EXIT_POLL_MS);\n\t\ttry {\n\t\t\tprocess.kill(holder.pid, 0);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ESRCH\") return;\n\t\t\tthrow new Error(`Failed to inspect ${holder.image} (PID ${holder.pid})`, { cause: error });\n\t\t}\n\t}\n\n\ttry {\n\t\tprocess.kill(holder.pid, \"SIGKILL\");\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && error.code === \"ESRCH\") return;\n\t\tthrow new Error(`Failed to kill ${holder.image} (PID ${holder.pid})`, { cause: error });\n\t}\n\tawait Bun.sleep(PROCESS_EXIT_POLL_MS);\n}\n\nasync function reclaimStatsPort(port: number): Promise<\"retry\"> {\n\tconst holder = await findPortHolder(port);\n\tif (!holder) {\n\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(\"\\\\\", \"/\");","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/stats/src/port-conflict.ts#L198-L234","documentation":"If the holder survives the SIGTERM poll window, terminatePortHolder() escalates to SIGKILL. A non-ESRCH failure of that kill is wrapped as 'Failed to kill <image> (PID <pid>)'. As with the other stages this almost always means the OS refused the signal — normally EPERM against a process the current user does not own.","triggerScenarios":"process.kill(holder.pid, 'SIGKILL') throwing EPERM/EINVAL after the process ignored SIGTERM for the full poll period, during reclaimStatsPort (called from prepareStatsPort/recoverStatsPort).","commonSituations":"A root-owned or another-user process squatting on the stats port and ignoring SIGTERM; zombies in uninterruptible sleep where even SIGKILL delivery fails; PID handed to a new process between polls with restrictive permissions.","solutions":["Kill the process manually with sudo: sudo kill -9 <pid>","Check ps -p <pid> -o user,stat to confirm ownership/state; if uninterruptible (D state), fix the kernel/IO hang first","Run omp stats as the same user as the port holder, or reconfigure so the port belongs to the current user","Choose a different port for the stats dashboard to avoid the conflict entirely"],"exampleFix":"// before\nawait prepareStatsPort(port); // throws 'Failed to kill omp (PID 1234)'\n// after\ntry {\n  await prepareStatsPort(port);\n} catch (err) {\n  if (err.message.includes('Failed to kill')) {\n    console.error(`Manual intervention needed: sudo kill -9 <pid>`);\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"const out = Bun.spawnSync(['ps', '-o', 'user=', '-p', String(pid)]);\nif (out.stdout.toString().trim() !== process.env.USER) {\n  console.error(`PID ${pid} owned by another user; SIGKILL will fail without sudo`);\n}","typeGuard":"function isNodeErrno(err: unknown): err is NodeJS.ErrnoException {\n  return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}","tryCatchPattern":"try {\n  await prepareStatsPort(port);\n} catch (err) {\n  if (err.message.startsWith('Failed to kill')) {\n    console.error(`${err.message} — escalate: sudo kill -9 <pid>, or switch ports`);\n  } else throw err;\n}","preventionTips":["Do not let root or service managers own the stats port","Verify holder ownership/state (ps -o user,stat) before reclamation attempts","Configure a dedicated stats port unlikely to collide with other services","If a process ignores SIGTERM and SIGKILL fails, investigate D-state/hangs rather than retrying"],"tags":["process","signal","permissions","port","sigkill"],"backgroundTag":"process-kill-eperm","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}