{"record":{"id":"32993d5f18224ff4","repo":"slopus/happy","slug":"daemon-run-failed-to-acquire-daemon-lock-daemon","errorCode":null,"errorMessage":"[DAEMON RUN] Failed to acquire daemon lock; daemon startup did not complete","messagePattern":"\\[DAEMON RUN\\] Failed to acquire daemon lock; daemon startup did not complete","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/happy-cli/src/daemon/run.ts","lineNumber":150,"sourceCode":"  // Check if running daemon version matches current CLI version\n  const runningDaemonVersionMatches = await isDaemonRunningCurrentlyInstalledHappyVersion();\n  if (!runningDaemonVersionMatches) {\n    // TODO: This hand-rolled self-restart path is awkward to reason about and awkward to test.\n    // We should probably migrate this daemon to native system service management\n    // (launchd/systemd, similar to OpenClaw's model), so startup/start-at-login and upgrades\n    // are owned by the OS instead of by the daemon trying to replace itself in-process.\n    logger.debug('[DAEMON RUN] Daemon version mismatch detected, restarting daemon with current CLI version');\n    await stopDaemon();\n  } else {\n    logger.debug('[DAEMON RUN] Daemon version matches, keeping existing daemon');\n    console.log('Daemon already running with matching version');\n    process.exit(0);\n  }\n\n  // Acquire exclusive lock (proves daemon is running)\n  const daemonLockHandle = await acquireDaemonLock(5, 200);\n  if (!daemonLockHandle) {\n    logger.warn('[DAEMON RUN] Failed to acquire daemon lock; daemon startup did not complete');\n    process.exit(1);\n  }\n\n  // At this point we should be safe to startup the daemon:\n  // 1. Not have a stale daemon state\n  // 2. Should not have another daemon process running\n\n  try {\n    // Happy Agent is a machine-level service shared by the mobile app and\n    // Happy Terminal. Start it concurrently and keep this daemon boot path\n    // independent from its install/download/network state.\n    startHappyTerminalDaemon();\n\n    // Start caffeinate\n    const caffeinateStarted = startCaffeinate();\n    if (caffeinateStarted) {\n      logger.debug('[DAEMON RUN] Sleep prevention enabled');\n    }","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/daemon/run.ts#L132-L168","documentation":"startDaemon() serializes daemon startup with an exclusive lock file (acquireDaemonLock, retried 5 times every 200ms). If the lock cannot be acquired, another daemon process is presumed to be starting/running (or a stale lock was left behind), so this process logs 'Failed to acquire daemon lock; daemon startup did not complete' and exits with code 1 instead of starting a second daemon.","triggerScenarios":"acquireDaemonLock(5, 200) returns null: another daemon instance is mid-startup or already holds the lock, a previous daemon crashed and left a stale lock file (O_EXCL creation fails), or filesystem permission problems prevent creating the lock file in the happy home directory.","commonSituations":"Running `happy daemon start` twice in quick succession (two detached processes race); daemon killed with SIGKILL (crash, OOM, machine power-off) without cleanup so the lock file survives; multiple-user/permission mismatch in ~/.happy; automation scripts launching daemons concurrently.","solutions":["Check whether a daemon is actually running: `happy daemon status` (or inspect daemon.state.json / ps). If it is, no action needed — the new start intentionally exits.","If no daemon is running, remove the stale lock file in the happy home directory (~/.happy or $HAPPY_HOME_DIR), then run `happy daemon start` again.","Run `happy doctor` (and `happy doctor clean` if needed) to find and kill runaway happy processes holding the lock.","Avoid launching the daemon concurrently from scripts; serialize daemon start calls and confirm HOME/HAPPY_HOME_DIR permissions are correct."],"exampleFix":"// before: stale lock after a SIGKILLed daemon\nhappy daemon start   # exits 1: Failed to acquire daemon lock\n// after: clean up, then restart\nhappy doctor clean   # or: rm ~/.happy/<daemon-lock-file>\nhappy daemon start","handlingStrategy":"validation","validationCode":"// Before starting the daemon, check whether one is already running\nimport { readFileSync, existsSync, unlinkSync } from 'fs';\nimport { configuration } from './configuration';\n\nconst stateFile = configuration.daemonStateFile; // daemon.state.json path\nif (existsSync(stateFile)) {\n  try {\n    const state = JSON.parse(readFileSync(stateFile, 'utf8'));\n    const alive = (() => { try { process.kill(state.pid, 0); return true; } catch { return false; } })();\n    if (alive) throw new Error(`Daemon already running (pid ${state.pid}) — no need to start`);\n    // stale state from a dead daemon: clean up lock/state before starting\n    unlinkSync(stateFile);\n  } catch { /* corrupt state: safe to remove */ }\n}","typeGuard":null,"tryCatchPattern":"// Wrap daemon start and interpret exit code 1 lock failure\nconst { status } = spawnSync('happy', ['daemon', 'start']);\nif (status === 1) {\n  // lock not acquired: either a daemon is already running or a stale lock exists.\n  // run `happy daemon status`; if nothing is running, `happy doctor clean`, remove the lock file, and retry once.\n}","preventionTips":["Don't launch `happy daemon start` from multiple scripts/processes at once; serialize daemon startups.","Prefer graceful `happy daemon stop` over SIGKILL so the lock file is released on shutdown.","After a crash or force-kill, run `happy doctor` / `doctor clean` to clear orphan processes and stale locks.","Verify write permissions on the happy home directory (~/.happy or $HAPPY_HOME_DIR)."],"tags":["daemon","lock","concurrency","stale-state"],"backgroundTag":"lock-acquisition-failed","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}