{"record":{"id":"3f8279be2326f527","repo":"Yeachan-Heo/oh-my-codex","slug":"exec-followup-queue-lock-timeout","errorCode":"exec_followup_queue_lock_timeout","errorMessage":"exec_followup_queue_lock_timeout","messagePattern":"exec_followup_queue_lock_timeout","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/exec/followup.ts","lineNumber":209,"sourceCode":"      } finally {\n        await rm(lockPath, { recursive: true, force: true });\n      }\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n\n      try {\n        const lockStat = await stat(lockPath);\n        if (Date.now() - lockStat.mtimeMs > QUEUE_LOCK_STALE_MS) {\n          await rm(lockPath, { recursive: true, force: true });\n          continue;\n        }\n      } catch (statError) {\n        if ((statError as NodeJS.ErrnoException).code === \"ENOENT\") continue;\n        throw statError;\n      }\n\n      if (Date.now() - start > QUEUE_LOCK_MAX_WAIT_MS) {\n        throw new Error(\"exec_followup_queue_lock_timeout\");\n      }\n      await sleep(QUEUE_LOCK_RETRY_MS);\n    }\n  }\n}\n\nexport async function injectExecFollowup(\n  options: InjectExecFollowupOptions,\n): Promise<InjectExecFollowupResult> {\n  const sessionId = normalizeSessionId(options.sessionId);\n  const prompt = normalizePrompt(options.prompt);\n  const actor = normalizeActor(options.actor);\n  const nowIso = options.nowIso ?? new Date().toISOString();\n\n  const active = await readUsableSessionState(options.cwd);\n  const activeUsable = active && isSessionStateUsable(active, options.cwd);\n  if (!activeUsable && !options.allowInactiveSession) {\n    throw new Error(\"job_not_input_accepting:no_active_exec_session\");","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/exec/followup.ts#L191-L227","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait injectExecFollowup(cwd, sessionId, { prompt }); // may throw exec_followup_queue_lock_timeout under contention\n\n// after\nfor (let attempt = 1; attempt <= 5; attempt += 1) {\n  try { await injectExecFollowup(cwd, sessionId, { prompt }); break; }\n  catch (e) { if ((e as Error).message !== 'exec_followup_queue_lock_timeout' || attempt === 5) throw e; await sleep(500 * attempt); }\n}","handlingStrategy":"retry","validationCode":"import { existsSync } from 'node:fs';\n// before injecting, ensure no other injector of yours is running per cwd\nif (existsSync(lockPathFor(cwd))) console.warn('queue lock present; may contend');","typeGuard":null,"tryCatchPattern":"try { await injectExecFollowup(cwd, sessionId, { prompt }); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'exec_followup_queue_lock_timeout') { await sleep(backoff); return retry(n - 1); }\n  throw e;\n}","preventionTips":["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."],"tags":["concurrency","lock-timeout","file-lock","retryable","exec-followup"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}