{"record":{"id":"100765f54cbeafa0","repo":"thedotmack/claude-mem","slug":"timed-out-waiting-for-the-observer-health-lock-up","errorCode":null,"errorMessage":"Timed out waiting for the observer-health lock; updating unlocked","messagePattern":"Timed out waiting for the observer-health lock; updating unlocked","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/shared/observer-health.ts","lineNumber":205,"sourceCode":"      } catch {\n        // Holder released between our failed create and the stat — retry.\n        continue;\n      }\n      if (Date.now() - mtimeMs > LEDGER_LOCK_STALE_MS) {\n        try {\n          unlinkSync(lockPath);\n        } catch {\n          // A competing breaker won, or the fs refused the delete; the retry\n          // loop re-evaluates either way.\n        }\n        continue;\n      }\n      sleepSync(LEDGER_LOCK_RETRY_MS);\n    }\n  }\n\n  if (!held && Date.now() >= deadline) {\n    logger.warn('SESSION', 'Timed out waiting for the observer-health lock; updating unlocked', { lockPath });\n  }\n\n  try {\n    return mutate();\n  } finally {\n    if (held) {\n      try {\n        unlinkSync(lockPath);\n      } catch {\n        // Already broken as stale by a waiter — nothing to release.\n      }\n    }\n  }\n}\n\nexport function recordObserverFailure(\n  provider: string,\n  error: string | ObserverFailureDetail,","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/8bc631a71a487424b866756e43a6efa4574cc66b/src/shared/observer-health.ts#L187-L223","documentation":"Observer-health ledger mutations take a file lock (lockPath) via a synchronous retry loop with a deadline. If the deadline passes without acquiring it, the code warns and performs mutate() anyway — the update still happens, with a small risk of interleaving with a competing circuit-breaker writer. The lock is only released in finally when it was actually held.","triggerScenarios":"Another process holds the ledger lock past the deadline: many concurrent hook processes mutating the same observer-health ledger, a slow filesystem dragging the retry loop, or GC/paging pauses on the holder.","commonSituations":"Bursty parallel Claude sessions each running hooks against one ledger; ledger stored on network/slow storage; a crashed holder whose lock got broken as stale, causing retry churn.","solutions":["Treat as usually benign — one unlocked ledger write; re-check observer health status afterwards to confirm consistency.","Reduce the number of simultaneously launched sessions that hammer the same ledger.","If persistent, inspect for a zombie holder: lsof on the lock path.","Restart the worker to clear lock contention."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"const held = acquireLedgerLockWithDeadline(lockPath, deadline);\nif (!held) {\n  // decide explicitly: proceed unlocked (current behavior) or skip\n  logger.warn('SESSION', 'Timed out waiting for the observer-health lock; updating unlocked', { lockPath });\n}","typeGuard":null,"tryCatchPattern":"try {\n  return mutate();\n} finally {\n  if (held) safeUnlink(lockPath);\n}","preventionTips":["Avoid launching many simultaneous hook processes against one observer-health ledger.","Keep the ledger on a local, fast filesystem — not network storage.","Periodically clear dead lock files; the stale-break logic handles it, but fewer waiters means fewer unlocked writes."],"tags":["file-lock","concurrency","observer-health","claude-mem"],"backgroundTag":"file-lock-timeout","analyzedSha":"8bc631a71a487424b866756e43a6efa4574cc66b","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}