Yeachan-Heo/oh-my-codex · error

exec_followup_queue_lock_timeout

exec_followup_queue_lock_timeout

Error message

exec_followup_queue_lock_timeout

What it means

Thrown by withQueueLock when it cannot acquire the exec followup queue lock within QUEUE_LOCK_MAX_WAIT_MS. The queue is guarded by a lock file to serialize concurrent injectors; if another process holds the lock (or a stale lock file was left behind), acquisition is retried until the deadline and then this error is raised.

Source

Thrown at src/exec/followup.ts:209

      } finally {
        await rm(lockPath, { recursive: true, force: true });
      }
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;

      try {
        const lockStat = await stat(lockPath);
        if (Date.now() - lockStat.mtimeMs > QUEUE_LOCK_STALE_MS) {
          await rm(lockPath, { recursive: true, force: true });
          continue;
        }
      } catch (statError) {
        if ((statError as NodeJS.ErrnoException).code === "ENOENT") continue;
        throw statError;
      }

      if (Date.now() - start > QUEUE_LOCK_MAX_WAIT_MS) {
        throw new Error("exec_followup_queue_lock_timeout");
      }
      await sleep(QUEUE_LOCK_RETRY_MS);
    }
  }
}

export async function injectExecFollowup(
  options: InjectExecFollowupOptions,
): Promise<InjectExecFollowupResult> {
  const sessionId = normalizeSessionId(options.sessionId);
  const prompt = normalizePrompt(options.prompt);
  const actor = normalizeActor(options.actor);
  const nowIso = options.nowIso ?? new Date().toISOString();

  const active = await readUsableSessionState(options.cwd);
  const activeUsable = active && isSessionStateUsable(active, options.cwd);
  if (!activeUsable && !options.allowInactiveSession) {
    throw new Error("job_not_input_accepting:no_active_exec_session");

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Retry the call after a delay — contention is usually transient; serialize your own callers with a queue/mutex so only one injector runs at a time per cwd.
  2. Check for and remove a stale lock file if no other omx process is running (verify with ps first).
  3. Reduce batch size or frequency of concurrent injections to the same directory.
  4. If this recurs regularly, inspect QUEUE_LOCK_MAX_WAIT_MS/QUEUE_LOCK_RETRY_MS constants and consider whether your workload needs a longer wait or distributed locking.

Example fix

// before
await injectExecFollowup(cwd, sessionId, { prompt }); // may throw exec_followup_queue_lock_timeout under contention

// after
for (let attempt = 1; attempt <= 5; attempt += 1) {
  try { await injectExecFollowup(cwd, sessionId, { prompt }); break; }
  catch (e) { if ((e as Error).message !== 'exec_followup_queue_lock_timeout' || attempt === 5) throw e; await sleep(500 * attempt); }
}
Defensive patterns

Strategy: retry

Validate before calling

import { existsSync } from 'node:fs';
// before injecting, ensure no other injector of yours is running per cwd
if (existsSync(lockPathFor(cwd))) console.warn('queue lock present; may contend');

Try / catch

try { await injectExecFollowup(cwd, sessionId, { prompt }); }
catch (e) {
  if (e instanceof Error && e.message === 'exec_followup_queue_lock_timeout') { await sleep(backoff); return retry(n - 1); }
  throw e;
}

Prevention

When it happens

Trigger: Two or more processes call injectExecFollowup or markExecFollowupsDelivered on the same working directory concurrently and one holds the lock longer than QUEUE_LOCK_MAX_WAIT_MS; a crashed process left a stale lock file behind; a slow or hung filesystem causes stat retry loop to exceed the deadline.

Common situations: Parallel CI jobs or shell loops injecting followups into the same repo checkout; a previously killed omx process leaving the lock file on disk; network filesystem latency inflating lock acquisition time; long-running consumers holding the lock while delivering many queued items.

Understand the failure class

Related errors


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