{"record":{"id":"7c8c92bab2d22832","repo":"slopus/happy","slug":"failed-to-acquire-settings-lock-after-max-lock-a","errorCode":null,"errorMessage":"Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1000} seconds","messagePattern":"Failed to acquire settings lock after (.+?) seconds","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/persistence.ts","lineNumber":182,"sourceCode":"        // Lock file exists, wait and retry\n        attempts++;\n        await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));\n\n        // Check for stale lock\n        try {\n          const stats = await stat(lockFile);\n          if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {\n            await unlink(lockFile).catch(() => { });\n          }\n        } catch { }\n      } else {\n        throw err;\n      }\n    }\n  }\n\n  if (!fileHandle) {\n    throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1000} seconds`);\n  }\n\n  try {\n    // Read current settings with defaults\n    const current = await readSettings() || { ...defaultSettings };\n\n    // Apply update\n    const updated = await updater(current);\n\n    // Ensure directory exists\n    if (!existsSync(configuration.happyHomeDir)) {\n      await mkdir(configuration.happyHomeDir, { recursive: true });\n    }\n\n    // Write atomically using rename\n    await writeFile(tmpFile, JSON.stringify(updated, null, 2));\n    await rename(tmpFile, configuration.settingsFile); // Atomic on POSIX\n","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/persistence.ts#L164-L200","documentation":"updateSettings() serializes settings writes with an exclusive file lock, retrying up to MAX_LOCK_ATTEMPTS with LOCK_RETRY_INTERVAL_MS between tries. If no lock handle is obtained after all retries, it throws to avoid concurrent processes corrupting the settings file. This is a contention/timeout error, not a data error.","triggerScenarios":"Another process holds the settings lock (e.g. a concurrent `happy` command, the daemon, or a crashed process that left a stale lock) for longer than MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS (total wait in seconds shown in the message).","commonSituations":"Multiple happy CLI instances or the daemon writing settings simultaneously; a previous run crashed leaving a stale lock; slow filesystem (network mount) making lock acquisition exceed the timeout; sandbox configure/disable handlers racing with the main process.","solutions":["Ensure no other happy processes are running (`pgrep -f happy`), then retry the command","Remove a stale lock file left by a crashed process (check the settings directory for the lock artifact) if you are certain nothing holds it","Increase MAX_LOCK_ATTEMPTS or LOCK_RETRY_INTERVAL_MS in persistence.ts if contention is expected","Serialize settings updates in your tooling so concurrent writers cannot occur"],"exampleFix":"// before\nawait Promise.all([updateSettings(a), updateSettings(b)]); // concurrent writers\n// after\nawait updateSettings(a);\nawait updateSettings(b); // sequential writes avoid lock contention","handlingStrategy":"retry","validationCode":"// Check no other happy process holds the lock\nconst { execSync } = require('child_process');\nif (execSync('pgrep -f happy || true').toString().trim()) {\n  await waitForOtherHappyProcessesToExit();\n}","typeGuard":null,"tryCatchPattern":"const MAX_RETRIES = 5;\nfor (let i = 0; i < MAX_RETRIES; i++) {\n  try {\n    await updateSettings(fn);\n    break;\n  } catch (err) {\n    if (err.message.startsWith('Failed to acquire settings lock') && i < MAX_RETRIES - 1) {\n      await new Promise(r => setTimeout(r, 2000));\n      continue;\n    }\n    throw err;\n  }\n}","preventionTips":["Avoid invoking multiple happy commands that write settings concurrently","Serialize settings updates in scripts rather than running them in parallel","After a crashed run, verify and clean stale lock files before retrying"],"tags":["filesystem","lock","concurrency","settings"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}