{"record":{"id":"dfccaad56d4c2ccc","repo":"slopus/happy","slug":"process-did-not-die-within-timeout","errorCode":null,"errorMessage":"Process did not die within timeout","messagePattern":"Process did not die within timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/daemon/controlClient.ts","lineNumber":271,"sourceCode":"    } catch (error) {\n      logger.debug('Daemon already dead');\n    }\n  } catch (error) {\n    logger.debug('Error stopping daemon', error);\n  }\n}\n\nasync function waitForProcessDeath(pid: number, timeout: number): Promise<void> {\n  const start = Date.now();\n  while (Date.now() - start < timeout) {\n    try {\n      process.kill(pid, 0);\n      await new Promise(resolve => setTimeout(resolve, 100));\n    } catch {\n      return; // Process is dead\n    }\n  }\n  throw new Error('Process did not die within timeout');\n}","sourceCodeStart":253,"sourceCodeEnd":272,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/daemon/controlClient.ts#L253-L272","documentation":"waitForProcessDeath polls the daemon PID with process.kill(pid, 0) every 100ms until the process is gone; if it is still alive when the loop's timeout elapses, it throws. stopDaemon calls this after sending a shutdown signal to confirm the daemon actually terminated.","triggerScenarios":"stopDaemon sends SIGTERM to the daemon PID but the process keeps running past the polling window — e.g. the daemon is stuck, ignoring the signal, or in uninterruptible I/O.","commonSituations":"Daemon hung on a blocking operation, zombie/orphaned process from a crashed parent, PID reused by an unrelated process, or a too-short timeout on a slow machine.","solutions":["Escalate to SIGKILL: kill -9 <pid> after the graceful timeout, then retry stopDaemon","Inspect the daemon with ps/logs to find why it ignored SIGTERM","Remove any stale PID file and restart the daemon cleanly","Report/patch: increase the polling timeout or add SIGKILL escalation in controlClient"],"exampleFix":"// before\nthrow new Error('Process did not die within timeout');\n// after\ntry {\n  process.kill(pid, 'SIGKILL');\n  await new Promise(resolve => setTimeout(resolve, 500));\n} catch { /* already gone */ }\nthrow new Error('Process did not die within timeout; sent SIGKILL');","handlingStrategy":"fallback","validationCode":"// Before stopping, confirm the daemon PID exists\ntry { process.kill(pid, 0); } catch { /* already dead, no need to wait */ }","typeGuard":null,"tryCatchPattern":"try {\n  await stopDaemon();\n} catch (err) {\n  if (err.message === 'Process did not die within timeout') {\n    try { process.kill(pid, 'SIGKILL'); } catch {}\n    // clean stale pid/state files and restart if needed\n  } else throw err;\n}","preventionTips":["Send SIGTERM and allow adequate grace before polling","Escalate to SIGKILL on timeout rather than failing","Detect and ignore zombie/reused PIDs (check process name)","Clean stale PID files when the daemon crashes"],"tags":["process","daemon","timeout"],"backgroundTag":"process-shutdown-timeout","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}