{"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/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/services/harness-flywheel-generations.ts#L112-L148","documentation":"The flywheel harness serializes generation-attempt accounting with the same O_EXCL + stale-mtime lock protocol used by the transaction service (`withAttemptsLock` in harness-flywheel-generations.ts). If the attempts lock exists, is younger than ATTEMPTS_LOCK_STALE_MS, and the caller's deadline expires while polling every 5ms, this error surfaces — protecting the attempt counters from concurrent mutation.","triggerScenarios":"Two harness/flywheel commands mutating generation attempts concurrently in one checkout (e.g. a promotion run racing a status or eval command), or a stale-but-not-yet-old lock left by a crashed run.","commonSituations":"Parallel CI jobs or scripts invoking flywheel generation commands on the same workspace; a previously killed run leaving a fresh lock behind; an interactive session plus an automated timer both triggering the harness.","solutions":["Serialize flywheel generation commands per checkout and retry after the current run finishes","Check for and terminate stray claude-flow processes, then let the stale window elapse or delete the attempts lock file in the flywheel state directory","Give parallel jobs separate workspaces instead of a shared checkout"],"exampleFix":"# before: two jobs race the same checkout in CI\n- run: npx ruflo flywheel promote & npx ruflo flywheel eval &\n\n# after: serialized\n- run: npx ruflo flywheel promote && npx ruflo flywheel eval","handlingStrategy":"retry","validationCode":"// Best-effort: detect contention before invoking harness generation commands.\nimport { existsSync, lstatSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nfunction flywheelLocksPresent(root: string): string[] {\n  const dir = join(root, '.claude-flow');\n  return existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.lock')) : [];\n}","typeGuard":null,"tryCatchPattern":"let done = false;\nfor (let attempt = 0; attempt < 3 && !done; attempt++) {\n  try {\n    await runFlywheelGeneration(root);\n    done = true;\n  } catch (e) {\n    if (e?.message !== 'timed out acquiring flywheel attempts lock') throw e;\n    await new Promise((r) => setTimeout(r, 15_000));\n  }\n}\nif (!done) throw new Error('flywheel attempts lock remained contended after retries');","preventionTips":["Serialize harness/flywheel commands per checkout (promote, eval, status mutations)","Use separate workspaces for parallel CI jobs","Kill stray claude-flow processes before scheduling new harness runs"],"tags":["concurrency","file-lock","timeout","flywheel","harness"],"backgroundTag":"file-lock-timeout","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}