{"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":265,"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":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/ruvnet/ruflo/blob/5234333c3462640ab348363ba4a142945fd2bc47/v3/@claude-flow/cli/src/services/flywheel-transaction.ts#L247-L283","documentation":"Flywheel transactions serialize cross-process access with an O_EXCL lock file (`transaction-state.lock`): exclusive creation wins the lock, the holder unlinks it in `finally`, and a lock whose mtime is older than 60s (LOCK_STALE_MS) is treated as abandoned and reclaimed. When the lock exists, is still fresh, and the caller's deadline (LOCK_TIMEOUT_MS = 10s) expires while polling every 5ms, this error is thrown.","triggerScenarios":"Two or more claude-flow processes in the same checkout both open a flywheel transaction (parallel CI shards on one workspace, a forgotten interactive session, a hung daemon) and the second polls past its 10s deadline; or a crashed holder left a lock younger than 60s, so stale-takeover cannot fire yet.","commonSituations":"CI matrices that share one workspace; a long promotion running in one terminal while another flywheel command runs elsewhere; a killed process whose lock has not yet aged past the 60s staleness window.","solutions":["Serialize the runs — run one mutating flywheel command per checkout at a time and retry once the current holder finishes","If no other process is active (`ps aux | grep claude-flow`), wait 60s for the stale-lock takeover, or manually delete `.claude-flow/flywheel-v1/transaction-state.lock` once the holder is confirmed dead","Kill zombie claude-flow/daemon processes still holding the lock","Give each CI shard its own workspace instead of sharing one checkout"],"exampleFix":"// before: fire-and-forget, no tolerance for a concurrent holder\nawait openFlywheelTransaction(root);\n\n// after: bounded retry around the lock deadline\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { return await openFlywheelTransaction(root); }\n  catch (e) {\n    if (e?.message !== 'timed out acquiring flywheel transaction lock') throw e;\n    await new Promise((r) => setTimeout(r, 15_000)); // > LOCK_TIMEOUT_MS\n  }\n}\nthrow new Error('flywheel transaction lock stayed contended');","handlingStrategy":"retry","validationCode":"import { lstatSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nconst LOCK_STALE_MS = 60_000;\n\n// Best-effort preflight: report whether another holder is active.\nfunction lockStatus(root: string): 'free' | 'stale' | 'held' {\n  const lock = join(root, '.claude-flow', 'flywheel-v1', 'transaction-state.lock');\n  if (!existsSync(lock)) return 'free';\n  const age = Date.now() - lstatSync(lock).mtimeMs;\n  return age > LOCK_STALE_MS ? 'stale' : 'held';\n}","typeGuard":null,"tryCatchPattern":"const DEADLINE_MINUTES = 2;\nconst deadline = Date.now() + DEADLINE_MINUTES * 60_000;\nlet delayMs = 5_000;\nwhile (true) {\n  try {\n    txn = await openFlywheelTransaction(root);\n    break;\n  } catch (e) {\n    if (e?.message !== 'timed out acquiring flywheel transaction lock') throw e;\n    if (Date.now() > deadline) throw e; // escalate after bounded retries\n    await new Promise((r) => setTimeout(r, delayMs));\n    delayMs = Math.min(delayMs * 2, 30_000);\n  }\n}","preventionTips":["Run one mutating flywheel command per checkout at a time","Do not share one workspace between parallel CI shards","Clean up crashed claude-flow processes before starting new runs","Remember the 60s staleness window: a killed process's lock blocks for up to a minute by design"],"tags":["concurrency","file-lock","timeout","flywheel"],"backgroundTag":"file-lock-timeout","analyzedSha":"5234333c3462640ab348363ba4a142945fd2bc47","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}