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
- 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.
- Check for and remove a stale lock file if no other omx process is running (verify with ps first).
- Reduce batch size or frequency of concurrent injections to the same directory.
- 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
- Serialize inject calls per working directory in your own code.
- Run one injector per checkout in CI; shard by directory not by process.
- Clean up stale lock files after crashes before retrying.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timed out waiting for ultragoal mutation lock at ${repoRelat
- autoresearch_active_run_exists
- timed out waiting for tmux extended-keys lease lock: ${lockP
- [native-assets] publication-lock-timeout: ${path}; elapsed=$
- Native hook transaction rollback preserved ${artifact.path}
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/3f8279be2326f527.
Report an issue: GitHub.