slopus/happy · error

Process did not die within timeout

Error message

Process did not die within timeout

What it means

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.

Source

Thrown at packages/happy-cli/src/daemon/controlClient.ts:271

    } catch (error) {
      logger.debug('Daemon already dead');
    }
  } catch (error) {
    logger.debug('Error stopping daemon', error);
  }
}

async function waitForProcessDeath(pid: number, timeout: number): Promise<void> {
  const start = Date.now();
  while (Date.now() - start < timeout) {
    try {
      process.kill(pid, 0);
      await new Promise(resolve => setTimeout(resolve, 100));
    } catch {
      return; // Process is dead
    }
  }
  throw new Error('Process did not die within timeout');
}

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Escalate to SIGKILL: kill -9 <pid> after the graceful timeout, then retry stopDaemon
  2. Inspect the daemon with ps/logs to find why it ignored SIGTERM
  3. Remove any stale PID file and restart the daemon cleanly
  4. Report/patch: increase the polling timeout or add SIGKILL escalation in controlClient

Example fix

// before
throw new Error('Process did not die within timeout');
// after
try {
  process.kill(pid, 'SIGKILL');
  await new Promise(resolve => setTimeout(resolve, 500));
} catch { /* already gone */ }
throw new Error('Process did not die within timeout; sent SIGKILL');
Defensive patterns

Strategy: fallback

Validate before calling

// Before stopping, confirm the daemon PID exists
try { process.kill(pid, 0); } catch { /* already dead, no need to wait */ }

Try / catch

try {
  await stopDaemon();
} catch (err) {
  if (err.message === 'Process did not die within timeout') {
    try { process.kill(pid, 'SIGKILL'); } catch {}
    // clean stale pid/state files and restart if needed
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/dfccaad56d4c2ccc. Report an issue: GitHub.