{"record":{"id":"e22466e7e9c976af","repo":"ruvnet/ruflo","slug":"timed-out-acquiring-flywheel-transaction-lock","errorCode":null,"errorMessage":"timed out acquiring flywheel transaction lock","messagePattern":"timed out acquiring flywheel transaction lock","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/flywheel-transaction.ts","lineNumber":258,"sourceCode":"    try {\n      const fd = fs.openSync(lock, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);\n      fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }), 'utf8');\n      fs.closeSync(fd);\n      try {\n        return await fn();\n      } finally {\n        try { fs.unlinkSync(lock); } catch { /* lock already gone */ }\n      }\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n      try {\n        const stat = fs.lstatSync(lock);\n        if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {\n          fs.unlinkSync(lock);\n          continue;\n        }\n      } catch { /* raced with owner */ }\n      if (Date.now() >= deadline) throw new Error('timed out acquiring flywheel transaction lock');\n      await delay(5);\n    }\n  }\n}\n\nfunction validateReceiptId(receiptId: string): void {\n  if (!/^sha256:[a-f0-9]{64}$/.test(receiptId)) throw new Error('invalid receipt ID');\n}\n\nfunction receiptPath(root: string, receiptId: string): string {\n  validateReceiptId(receiptId);\n  return path.join(receiptDir(root), `${receiptId.slice('sha256:'.length)}.json`);\n}\n\nexport function readFlywheelReceipt(root: string, receiptId: string): FlywheelEvaluationReceipt | null {\n  try {\n    const file = receiptPath(root, receiptId);\n    assertSafeFile(file);","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/flywheel-transaction.ts#L240-L276","documentation":"Thrown by withStateLock() when the O_EXCL lock file (.claude-flow/flywheel-v1/transaction-state.lock) cannot be acquired within LOCK_TIMEOUT_MS (10 seconds). The lock serialises all cross-process mutation of the authoritative promotion state. Stale locks older than LOCK_STALE_MS (60s) are automatically taken over, so this timeout means a live process held the lock for the full 10s window.","triggerScenarios":"Two or more processes concurrently promoting/reading-modifying-writing the flywheel transaction state; a long-running commit (e.g. slow fsync or policy materialization) holding the lock beyond 10s; heavy I/O contention on the state directory's filesystem.","commonSituations":"Parallel CI jobs or multiple daemon instances hitting the same project root's state; a hung applyFn (policy materialization callback) holding the lock; network filesystem latency on the state dir.","solutions":["Serialize promotion transactions — only one writer per project root at a time.","Identify and stop the process holding the lock (check the PID inside transaction-state.lock).","If the holder is a confirmed zombie, remove the stale lock file manually after verifying the PID is dead.","Move the state directory off network/low-IOPS storage (it defaults to <projectRoot>/.claude-flow/flywheel-v1/).","Ensure applyFn callbacks (policy materialization) complete quickly."],"exampleFix":"# diagnose\nps -p $(cat .claude-flow/flywheel-v1/transaction-state.lock | jq .pid)\n# if PID is dead, remove stale lock\nrm .claude-flow/flywheel-v1/transaction-state.lock","handlingStrategy":"retry","validationCode":"function isLockHeldByLiveProcess(lockFile: string): boolean {\n  try {\n    const { pid } = JSON.parse(fs.readFileSync(lockFile, 'utf8'));\n    return pid && isProcessAlive(pid);\n  } catch { return false; }\n}\nfunction isProcessAlive(pid: number): boolean {\n  try { process.kill(pid, 0); return true; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await promoteFlywheel(root, receipt, opts);\n} catch (e) {\n  if (e instanceof Error && /timed out acquiring flywheel transaction lock/.test(e.message)) {\n    // inspect the holder; if dead, remove stale lock and retry ONCE\n    if (!isLockHeldByLiveProcess(lockPath(root))) {\n      fs.unlinkSync(lockPath(root));\n      await promoteFlywheel(root, receipt, opts);\n    } else {\n      throw new Error('flywheel lock held by a live process — serialize promotions');\n    }\n  } else throw e;\n}","preventionTips":["Serialize promotion transactions — one writer per project root.","Keep the state dir on fast local storage, not NFS.","Ensure applyFn (policy materialization) callbacks are fast.","Monitor for orphaned daemon processes that may hold locks."],"tags":["concurrency","locking","filesystem","timeout","transaction"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}