{"record":{"id":"a5e8ba80b77d27c5","repo":"Yeachan-Heo/oh-my-codex","slug":"timed-out-waiting-for-ultragoal-mutation-lock-at","errorCode":null,"errorMessage":"Timed out waiting for ultragoal mutation lock at ${repoRelative(cwd, lockPath)}.","messagePattern":"Timed out waiting for ultragoal mutation lock at (.+?)\\.","errorType":"exception","errorClass":"UltragoalError","httpStatus":null,"severity":"error","filePath":"src/ultragoal/artifacts.ts","lineNumber":956,"sourceCode":"  options: { allowUnboundEnvironment?: boolean } = {},\n): Promise<T> {\n  const beforeLock = await assertUltragoalWritableLifecycleAuthority(cwd, options);\n  await mkdir(ultragoalDir(cwd), { recursive: true });\n  const lockPath = join(ultragoalDir(cwd), ULTRAGOAL_MUTATION_LOCK);\n  let handle: Awaited<ReturnType<typeof open>> | undefined;\n  for (let attempt = 0; attempt < 100; attempt += 1) {\n    try {\n      handle = await open(lockPath, 'wx');\n      await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: iso() }));\n      break;\n    } catch (error) {\n      const code = (error as NodeJS.ErrnoException).code;\n      if (code !== 'EEXIST') throw error;\n      await sleep(Math.min(25 + attempt * 5, 250));\n    }\n  }\n  if (!handle) {\n    throw new UltragoalError(`Timed out waiting for ultragoal mutation lock at ${repoRelative(cwd, lockPath)}.`);\n  }\n  try {\n    // The post-lock comparison addresses pointer changes while waiting for this\n    // lock only. A SessionStart publication can still land after it and before\n    // the operation's filesystem writes.\n    const afterLock = await assertUltragoalWritableLifecycleAuthority(cwd, options);\n    if (!writableAuthorityEquals(beforeLock, afterLock)) {\n      throw new UltragoalError(\n        `Refusing durable ultragoal mutation after writable lifecycle authority drift while waiting for the mutation lock: before lock ${describeWritableAuthority(beforeLock)}; after lock ${describeWritableAuthority(afterLock)}.`,\n      );\n    }\n    return await operation();\n  } finally {\n    await handle.close().catch(() => undefined);\n    await rm(lockPath, { force: true }).catch(() => undefined);\n  }\n}\n","sourceCodeStart":938,"sourceCodeEnd":974,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/ultragoal/artifacts.ts#L938-L974","documentation":"Durable ultragoal mutations serialize through a file lock (open with O_EXCL in a retry loop with backoff capped at 250ms). If the lock file at lockPath cannot be acquired within the allotted attempts — because another process holds it or a stale lock file was left behind after a crash — this UltragoalError is thrown so the caller can retry rather than block forever.","triggerScenarios":"Calling any mutation wrapped by withUltragoalMutationLock while another long-running process (concurrent bench, another agent) holds the lock; or after a hard crash left the lock file on disk without an owner, exhausting retries with EEXIST every time.","commonSituations":"Parallel agents/benches mutating the same ultragoal state; a SIGKILLed process leaving a stale lock file; NFS/network filesystems where exclusive create semantics are flaky; very slow filesystems exceeding the total retry window.","solutions":["Retry the operation with backoff — the lock is advisory and short-lived, so transient contention resolves itself","Check for a stale lock file at the repo-relative path in the error message and delete it if no other process holds it (lsof/fuser to confirm)","Serialize ultragoal mutations across your processes (queue them) instead of racing many writers","If contention is chronic, reduce the number of concurrent mutators or lengthen your own outer timeout around the call"],"exampleFix":"// before\nawait appendStory(state, goal); // under contention -> timed out waiting for lock\n\n// after\nfor (let i = 0; i < 5; i++) {\n  try { await appendStory(state, goal); break; }\n  catch (e) { if (!isLockTimeout(e) || i === 4) throw e; await sleep(500 * 2 ** i); }\n}","handlingStrategy":"retry","validationCode":"import { existsSync } from 'node:fs';\n\nfunction lockLooksStale(lockPath: string): boolean {\n  // only treat as stale if no other ultragoal process is running\n  return existsSync(lockPath);\n}","typeGuard":"function isLockTimeoutError(e: unknown): boolean {\n  return e instanceof UltragoalError && e.message.includes('Timed out waiting for ultragoal mutation lock');\n}","tryCatchPattern":"for (let attempt = 0; attempt < 5; attempt++) {\n  try {\n    return await mutateUltragoalState(...);\n  } catch (e) {\n    if (!isLockTimeoutError(e) || attempt === 4) throw e;\n    await sleep(250 * 2 ** attempt);\n  }\n}","preventionTips":["Queue ultragoal mutations instead of racing many concurrent writers","Remove stale lock files only after confirming no live process holds them","Use exponential backoff retries around mutation APIs"],"tags":["ultragoal","file-lock","concurrency","timeout","retry"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}