{"record":{"id":"045a8116a3bb1aec","repo":"thedotmack/claude-mem","slug":"tree-kill-failed-for-pid-pid-error-instanceo","errorCode":null,"errorMessage":"tree-kill failed for PID ${pid}: ${error instanceof Error ? error.message : String(error)}","messagePattern":"tree-kill failed for PID (.+?): (.+?)","errorType":"exception","errorClass":"ProcessTreeKillError","httpStatus":null,"severity":"error","filePath":"src/shared/kill-process-tree.ts","lineNumber":282,"sourceCode":"        // Already dead — fine.\n      }\n    }\n    // In immediate mode the root already received SIGKILL in the pass above,\n    // and SIGKILL is not survivable — re-sending it would be pure noise (and\n    // would misrepresent the signal sequence to anything observing it).\n    if (!immediate && rootIsIntact()) {\n      try {\n        process.kill(pid, 'SIGKILL');\n      } catch {\n        // Already dead — fine.\n      }\n    }\n  } catch (error) {\n    // The individual signal calls above already tolerate ESRCH themselves, so\n    // anything surfacing here is a genuine failure of the teardown. Swallowing\n    // it would contradict this function's documented contract (and let\n    // `server stop` claim success over a kill that did not happen).\n    throw new ProcessTreeKillError(\n      pid,\n      `tree-kill failed for PID ${pid}: ${error instanceof Error ? error.message : String(error)}`,\n      { cause: error }\n    );\n  }\n}\n\n/** Descendant PID paired with the start token that proves its identity. */\nexport interface DescendantIdentity {\n  pid: number;\n  startToken: string | null;\n}\n\n/** One process-table row: discovery and identity from a SINGLE observation. */\ninterface ProcessTableRow {\n  pid: number;\n  ppid: number;\n  startToken: string | null;","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/8bc631a71a487424b866756e43a6efa4574cc66b/src/shared/kill-process-tree.ts#L264-L300","documentation":"killProcessTree throws ProcessTreeKillError on POSIX when the teardown sequence itself fails — descendant enumeration via the process table, signaling, or the graceful-settle logic threw something other than tolerated ESRCH. Per the documented contract, an already-dead target is not an error; only a genuine teardown failure reaches this throw so `server stop` cannot claim success over a kill that did not happen.","triggerScenarios":"Calling killProcessTree(pid) on Linux/macOS when collectDescendantIdentities throws (unexpected ps/pgrep failure beyond the internal degrade-to-single-kill handling), process.kill rejects with something other than ESRCH (e.g. EPERM on a foreign-UID descendant), or the settle/snapshot logic fails.","commonSituations":"Killing a subprocess tree where some descendants run under a different user (EPERM); /proc unreadable on hardened Linux; ps output malformed or locale issues breaking enumeration; resource exhaustion during the multi-snapshot walk.","solutions":["Check the inner error (err.cause) for EPERM — run the stopper as the same user that owns the child processes","Retry killProcessTree once; transient process-table races resolve on a second pass","Fall back to process.kill(pid,'SIGKILL') on the root and then sweep leftover descendants with collectDescendantPids","Verify platform tooling: ps/pgrep present and PATH sane on POSIX, since enumeration failure degrades the kill"],"exampleFix":"// before\nawait killProcessTree(workerPid);\n// after\ntry {\n  await killProcessTree(workerPid);\n} catch (e) {\n  if (e instanceof ProcessTreeKillError) {\n    console.error(`failed to stop PID ${e.pid}:`, e.cause ?? e);\n    // fallback: single-PID SIGKILL\n    try { process.kill(e.pid, 'SIGKILL'); } catch {}\n  }\n}","handlingStrategy":"try-catch","validationCode":"// ensure you can signal the target before teardown\ntry { process.kill(pid, 0); } catch (e) {\n  if ((e as NodeJS.ErrnoException).code === 'ESRCH') return; // already gone\n  if ((e as NodeJS.ErrnoException).code === 'EPERM') throw new Error('insufficient permission to stop process');\n}","typeGuard":"function isProcessTreeKillError(e: unknown): e is ProcessTreeKillError {\n  return e instanceof ProcessTreeKillError || (e instanceof Error && e.name === 'ProcessTreeKillError');\n}","tryCatchPattern":"try {\n  await killProcessTree(pid);\n} catch (e) {\n  if (isProcessTreeKillError(e)) {\n    logger.error(`teardown failed for ${e.pid}`, { cause: e.cause });\n    // fallback: direct root SIGKILL, then sweep descendants\n    try { process.kill(e.pid, 'SIGKILL'); } catch {}\n    for (const d of await collectDescendantPids(e.pid)) {\n      try { process.kill(d, 'SIGKILL'); } catch {}\n    }\n  }\n}","preventionTips":["Run the stopper under the same user that owns the child processes (avoid EPERM)","Pass expectedStartToken when you captured the PID before an await","Treat only ESRCH as 'already dead'; surface every other kill error","Verify ps/pgrep availability and pinned locale (LC_ALL=C) in constrained environments"],"tags":["posix","process-kill","subprocess","signal"],"backgroundTag":"permission-denied","analyzedSha":"8bc631a71a487424b866756e43a6efa4574cc66b","analyzedAt":"2026-09-09T10:47:06.009Z","contentChangedAt":"2026-09-09T10:47:06.009Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}