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

  1. 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
  2. Reduce concurrency: serialize hook invocations that write AGENTS.md so only one holds the lock at a time
  3. Check for a hung process holding the lock (ps / lsof) and terminate it
  4. 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

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

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/26e2e80c72c6c81d. Report an issue: GitHub.