Yeachan-Heo/oh-my-codex · error · Error
Failed to acquire AGENTS.md lock within timeout
Error message
Failed to acquire AGENTS.md lock within timeout
What it means
Thrown by acquireLock in the AGENTS.md overlay hook when it cannot acquire the advisory lock directory within its timeout budget (it retries every 100ms then gives up). The library explicitly fails rather than silently proceeding, so concurrent writers to AGENTS.md don't clobber each other.
Source
Thrown at src/hooks/agents-overlay.ts:104
const ownerFile = join(lock, "owner.json");
const ownerData = JSON.parse(await readFile(ownerFile, "utf-8"));
try {
process.kill(ownerData.pid, 0);
} catch {
// Owner PID is dead, safe to reap
await rm(lock, { recursive: true, force: true }).catch(() => {});
continue; // Retry acquire immediately
}
} catch (err) {
process.stderr.write(
`[agents-overlay] lock owner check failed: ${err}\n`,
);
}
await new Promise((r) => setTimeout(r, 100));
}
}
// Timeout: do NOT silently proceed - throw so caller knows lock failed
throw new Error("Failed to acquire AGENTS.md lock within timeout");
}
async function releaseLock(cwd: string): Promise<void> {
try {
await rm(lockPath(cwd), { recursive: true, force: true });
} catch (err) {
process.stderr.write(`[agents-overlay] release lock failed: ${err}\n`);
}
}
async function withAgentsMdLock<T>(
cwd: string,
fn: () => Promise<T>,
): Promise<T> {
await acquireLock(cwd);
try {
return await fn();
} finally {View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Remove the stale lock directory (the path from lockPath(cwd), typically under the repo's hook state dir) if no other process is running, then retry
- Reduce concurrency: serialize hook invocations that write AGENTS.md so only one holds the lock at a time
- Check for a hung process holding the lock (ps / lsof) and terminate it
- If on a slow/shared filesystem, increase the lock timeout if configurable, or move the repo to local disk
Example fix
// before
await withAgentsMdLock(cwd, writeOverlay);
// after
// clear stale lock when no other instance is running
await rm(lockDirPath, { recursive: true, force: true });
await withAgentsMdLock(cwd, writeOverlay); Defensive patterns
Strategy: retry
Validate before calling
import { stat } from 'node:fs/promises';
async function lockLikelyFree(cwd: string): Promise<boolean> {
try { await stat(lockPathFor(cwd)); return false; } catch { return true; }
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try { return await withAgentsMdLock(cwd, fn); }
catch (err) {
if (!(err instanceof Error) || !err.message.includes('AGENTS.md lock')) throw err;
await new Promise(r => setTimeout(r, 250 * (attempt + 1)));
}
}
throw new Error('AGENTS.md overlay lock unavailable after retries'); Prevention
- Serialize AGENTS.md-writing hooks across processes (queue, single writer)
- Clean up stale lock directories after crashes in a startup health check
- Avoid running many agent sessions concurrently in the same checkout
When it happens
Trigger: Calling withAgentsMdLock while another process/hook holds the .agents-md lock directory for longer than the timeout; a stale lock directory left behind by a crashed process; heavy parallel hook invocations (e.g. many concurrent agent sessions) exceeding the retry window.
Common situations: Multiple opencode/agent instances started simultaneously in the same repo; a previous run killed with SIGKILL leaving a stale lock dir; slow filesystems (NFS, containers) where lock dir creation and the 100ms retry loop exceed the timeout.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- timed out waiting for tmux extended-keys lease lock: ${lockP
- [native-assets] incomplete lock write
- [native-assets] lock readback mismatch
- [native-assets] publication-lock-timeout: ${path}; elapsed=$
- Session pointer committed, but lock release left recovery ev
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/26e2e80c72c6c81d.
Report an issue: GitHub.