can1357/oh-my-pi · error · Error
Failed to acquire lock for ${filePath} after ${opts.retries}
Error message
Failed to acquire lock for ${filePath} after ${opts.retries} attempts What it means
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.
Source
Thrown at packages/utils/src/file-lock.ts:42
return `${path.resolve(filePath)}.lock`;
}
function tryAcquireLock(lockPath: string): NativeFileLock | null {
const lock = NativeFileLock.tryAcquire(lockPath);
return lock.acquired ? lock : null;
}
async function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<NativeFileLock> {
const opts = { ...DEFAULT_OPTIONS, ...options };
const lockPath = getLockPath(filePath);
for (let attempt = 0; attempt < opts.retries; attempt++) {
const lock = tryAcquireLock(lockPath);
if (lock) return lock;
if (attempt + 1 < opts.retries) await Bun.sleep(opts.retryDelayMs);
}
throw new Error(`Failed to acquire lock for ${filePath} after ${opts.retries} attempts`);
}
/** Run `fn` while holding an OS-backed exclusive lock for `filePath`. */
export async function withFileLock<T>(
filePath: string,
fn: () => Promise<T>,
options: FileLockOptions = {},
): Promise<T> {
const lock = await acquireLock(filePath, options);
try {
return await fn();
} finally {
lock.release();
}
}
/**
* Test-only acquisition handle for forcing ownership handoffs. This is notView on GitHub (pinned to 9690622007)
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
Example fix
// before
await withFileLock(path, writeState); // default 50 x 100ms
// after
await withFileLock(path, writeState, { retries: 200, retryDelayMs: 250 }); // ~50s window for heavy contention Defensive patterns
Strategy: try-catch
Validate before calling
// No pre-check can prove the lock is free (TOCTOU); configure a sane window instead.
const opts = { retries: 100, retryDelayMs: 100 }; // ~10s budget Try / catch
try {
return await withFileLock(path, fn);
} catch (err) {
if (err instanceof Error && err.message.startsWith("Failed to acquire lock for")) {
// fallback: skip, queue, or surface 'resource busy'
return fallbackValue;
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- secret placeholder key at ${keyPath} exists but is empty or
- Unpacked ASAR file '${label}' changed while being read
- unknown filetype: {ft_debug}
- Is a directory
- Too many levels of symbolic links
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/fce42d55cf47bb7d.
Report an issue: GitHub.