{"record":{"id":"a8902e3f031ccb8a","repo":"ruvnet/ruflo","slug":"timed-out-acquiring-flywheel-attempts-lock","errorCode":null,"errorMessage":"timed out acquiring flywheel attempts lock","messagePattern":"timed out acquiring flywheel attempts lock","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/harness-flywheel-generations.ts","lineNumber":130,"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() }), 'utf-8');\n      fs.closeSync(fd);\n      try {\n        return 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 > ATTEMPTS_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 attempts lock');\n      await delay(5);\n    }\n  }\n}\n\n/** The current operating champion (last promotion's config), or defaults. */\nexport function currentChampion(root: string): { config: Record<string, number>; hash: string | null; generation: number } {\n  const p = loadPromotions(root);\n  if (!p.length) return { config: { ...DEFAULT_CONFIG }, hash: null, generation: 0 };\n  const last = p[p.length - 1];\n  return { config: (last.candidateManifest.policy.value ?? { ...DEFAULT_CONFIG }) as Record<string, number>, hash: last.candidateManifestHash, generation: p.length };\n}\n\nexport interface ServedState { championHash: string | null; config: Record<string, number> | null; servedAt: number | null; fromGeneration: number | null; }\nexport function servedChampion(root: string): ServedState {\n  return readJson<ServedState>(path.join(dir(root), SERVED_FILE)) ?? { championHash: null, config: null, servedAt: null, fromGeneration: null };\n}\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/harness-flywheel-generations.ts#L112-L148","documentation":"Thrown by withAttemptsLock() in harness-flywheel-generations.ts when the O_EXCL attempts lock cannot be acquired within ATTEMPTS_LOCK_TIMEOUT_MS. This lock serializes the read-testIndex→build-bundle→append critical section (ADR-381 §3): attempts.jsonl's length IS the sequential-evidence test stream position, so concurrent readers would both get the same index and silently double-spend alpha_k.","triggerScenarios":"Two concurrent runFlywheelGeneration() calls against the same project root; a process holding attempts.lock beyond the deadline; slow I/O during the append-to-attempts.jsonl phase.","commonSituations":"Parallel CI pipelines or multiple daemon ticks triggering generation simultaneously; a hung build-bundle phase; network filesystem on the flywheel generations state dir.","solutions":["Serialize flywheel generation runs — one generation per project root at a time.","Check the PID inside attempts.lock and confirm the holder is alive.","Remove a confirmed-stale lock (older than ATTEMPTS_LOCK_STALE_MS) manually.","Ensure the generations state directory is on fast local storage."],"exampleFix":"# diagnose\nps -p \"$(cat .claude-flow/flywheel-generations/attempts.lock | jq .pid)\"\n# if the PID is dead, remove the stale lock\nrm .claude-flow/flywheel-generations/attempts.lock","handlingStrategy":"retry","validationCode":"function attemptsLockHeldByLiveProcess(root: string, lockFile: string): boolean {\n  try {\n    const { pid } = JSON.parse(fs.readFileSync(lockFile, 'utf8'));\n    try { process.kill(pid, 0); return true; } catch { return false; }\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runFlywheelGeneration(root, ...);\n} catch (e) {\n  if (e instanceof Error && /timed out acquiring flywheel attempts lock/.test(e.message)) {\n    if (!attemptsLockHeldByLiveProcess(root, lockFile)) {\n      fs.unlinkSync(lockFile);\n      await runFlywheelGeneration(root, ...); // retry once\n    } else {\n      throw new Error('generation in progress — serialize flywheel runs');\n    }\n  } else throw e;\n}","preventionTips":["Run one flywheel generation per project root at a time.","Keep the generations state dir on local storage.","Ensure the build-bundle phase completes without hanging.","Never run parallel daemon ticks that both trigger generation."],"tags":["concurrency","locking","filesystem","sequential-evidence","timeout","generations"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}