{"record":{"id":"fce42d55cf47bb7d","repo":"can1357/oh-my-pi","slug":"failed-to-acquire-lock-for-filepath-after-opt","errorCode":null,"errorMessage":"Failed to acquire lock for ${filePath} after ${opts.retries} attempts","messagePattern":"Failed to acquire lock for (.+?) after (.+?) attempts","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/utils/src/file-lock.ts","lineNumber":42,"sourceCode":"\treturn `${path.resolve(filePath)}.lock`;\n}\n\nfunction tryAcquireLock(lockPath: string): NativeFileLock | null {\n\tconst lock = NativeFileLock.tryAcquire(lockPath);\n\treturn lock.acquired ? lock : null;\n}\n\nasync function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<NativeFileLock> {\n\tconst opts = { ...DEFAULT_OPTIONS, ...options };\n\tconst lockPath = getLockPath(filePath);\n\n\tfor (let attempt = 0; attempt < opts.retries; attempt++) {\n\t\tconst lock = tryAcquireLock(lockPath);\n\t\tif (lock) return lock;\n\t\tif (attempt + 1 < opts.retries) await Bun.sleep(opts.retryDelayMs);\n\t}\n\n\tthrow new Error(`Failed to acquire lock for ${filePath} after ${opts.retries} attempts`);\n}\n\n/** Run `fn` while holding an OS-backed exclusive lock for `filePath`. */\nexport async function withFileLock<T>(\n\tfilePath: string,\n\tfn: () => Promise<T>,\n\toptions: FileLockOptions = {},\n): Promise<T> {\n\tconst lock = await acquireLock(filePath, options);\n\ttry {\n\t\treturn await fn();\n\t} finally {\n\t\tlock.release();\n\t}\n}\n\n/**\n * Test-only acquisition handle for forcing ownership handoffs. This is not","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/file-lock.ts#L24-L60","documentation":"acquireLock uses an OS-backed exclusive lock (abstract Unix socket / named mutex / flock) and retries acquisition opts.retries times with opts.retryDelayMs between attempts (defaults: 50 retries x 100ms = ~5s). If the lock stays held the whole time, it throws this error naming the contended file path. It indicates another live process (or a hung one) still owns the lock for that resource.","triggerScenarios":"Calling withFileLock(filePath, fn) or acquireLock while another process holds the lock on `${filePath}.lock` for longer than the total retry window — e.g. retries: 50, retryDelayMs: 100 exhausted.","commonSituations":"Two agent instances writing the same session/state file; a crashed-but-alive background process holding the lock indefinitely; a long-running worker exceeding the 5s default window; NFS environments where flock semantics degrade.","solutions":["Increase retries/retryDelayMs in FileLockOptions if contention is expected and short-lived","Find and stop the process holding the lock (locks are released automatically on process exit, so a persistent holder is a live process)","Serialize work at a higher level (queue) instead of hammering the same lock file","Ensure the critical section under the lock is short so holders release promptly"],"exampleFix":"// before\nawait withFileLock(path, writeState); // default 50 x 100ms\n// after\nawait withFileLock(path, writeState, { retries: 200, retryDelayMs: 250 }); // ~50s window for heavy contention","handlingStrategy":"try-catch","validationCode":"// No pre-check can prove the lock is free (TOCTOU); configure a sane window instead.\nconst opts = { retries: 100, retryDelayMs: 100 }; // ~10s budget","typeGuard":null,"tryCatchPattern":"try {\n  return await withFileLock(path, fn);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Failed to acquire lock for\")) {\n    // fallback: skip, queue, or surface 'resource busy'\n    return fallbackValue;\n  }\n  throw err;\n}","preventionTips":["Keep critical sections under the lock short","Raise retries/retryDelayMs when contention is expected","Investigate any persistent holder — locks auto-release on process exit, so a lasting holder is a live or wedged process","Avoid the same lock file across unrelated long-running workers"],"tags":["concurrency","filesystem","lock","contention"],"backgroundTag":"file-lock-contention","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}