Yeachan-Heo/oh-my-codex · critical · UltragoalError
Refusing durable ultragoal mutation after writable lifecycle
Error message
Refusing durable ultragoal mutation after writable lifecycle authority drift while waiting for the mutation lock: before lock ${describeWritableAuthority(beforeLock)}; after lock ${describeWritableAuthority(afterLock)}. What it means
Before and after acquiring the mutation lock, the library snapshots the writable lifecycle authority (session binding). If they differ, a session publication (e.g. SessionStart) landed while this process waited for the lock, and the mutation would write into state whose ownership just changed — so it refuses with both before/after snapshots for diagnosis. This is a deliberate TOCTOU guard preserving durable-state integrity.
Source
Thrown at src/ultragoal/artifacts.ts:964
handle = await open(lockPath, 'wx');
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: iso() }));
break;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== 'EEXIST') throw error;
await sleep(Math.min(25 + attempt * 5, 250));
}
}
if (!handle) {
throw new UltragoalError(`Timed out waiting for ultragoal mutation lock at ${repoRelative(cwd, lockPath)}.`);
}
try {
// The post-lock comparison addresses pointer changes while waiting for this
// lock only. A SessionStart publication can still land after it and before
// the operation's filesystem writes.
const afterLock = await assertUltragoalWritableLifecycleAuthority(cwd, options);
if (!writableAuthorityEquals(beforeLock, afterLock)) {
throw new UltragoalError(
`Refusing durable ultragoal mutation after writable lifecycle authority drift while waiting for the mutation lock: before lock ${describeWritableAuthority(beforeLock)}; after lock ${describeWritableAuthority(afterLock)}.`,
);
}
return await operation();
} finally {
await handle.close().catch(() => undefined);
await rm(lockPath, { force: true }).catch(() => undefined);
}
}
async function appendLedger(cwd: string, entry: UltragoalLedgerEntry): Promise<void> {
await mkdir(ultragoalDir(cwd), { recursive: true });
const path = ultragoalLedgerPath(cwd);
await appendFile(path, `${JSON.stringify(entry)}\n`);
}
/** Pure plan read: no durable writes, no migration. */
async function readUltragoalPlanFile(cwd: string): Promise<UltragoalPlan> {View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Retry the whole mutation after re-reading the current session binding (export the new OMX_SESSION_ID) — the drift is usually a legitimate session rotation
- Coordinate process lifecycle: do not start/restart sessions while ultragoal mutations are in flight (barrier or queue around SessionStart)
- Inspect the before/after snapshots in the message to confirm which side is authoritative, then align the environment to it
- If drift recurs, check for two competing session publishers (duplicate orchestrators) writing session.json
Example fix
// before await appendStory(state, goal); // another session published while we waited -> refused // after await refreshSessionBinding(); // re-read session.json -> OMX_SESSION_ID await appendStory(state, goal); // now consistent under current authority
Defensive patterns
Strategy: retry
Type guard
function isAuthorityDriftError(e: unknown): boolean {
return e instanceof UltragoalError && e.message.includes('authority drift while waiting for the mutation lock');
} Try / catch
try {
return await mutateUltragoalState(...);
} catch (e) {
if (isAuthorityDriftError(e)) {
await refreshSessionBinding(); // align OMX_SESSION_ID to the 'after lock' snapshot in the message
return mutateUltragoalState(...); // safe: pre-lock check re-runs under new authority
}
throw e;
} Prevention
- Do not start/restart sessions while ultragoal mutations are in flight; use a barrier
- Retry once after re-reading the session binding — drift usually reflects a legitimate rotation
- Ensure only one orchestrator publishes sessions per repo to avoid competing SessionStart writers
When it happens
Trigger: A process acquires the mutation lock after waiting, and assertUltragoalWritableLifecycleAuthority returns a different authority than the pre-lock snapshot — typically because a concurrent SessionStart publication rewrote session.json/OMX_SESSION_ID authority in the gap between the two checks.
Common situations: Two sessions starting simultaneously against the same repo; a new bench bootstrapping (publishing SessionStart) while an old one is mid-mutation; orchestration scripts restarting agents without coordinating with in-flight ultragoal writes.
Related errors
- Refusing a durable ultragoal mutation before writable lifecy
- invalid auth slot path
- auth directory must not be a symlink: ${dir}
- ${label} is not a file: ${path}
- Refusing to overwrite existing ${repoRelative(cwd, missionPa
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/e0f1c8cd7cdf8fba.
Report an issue: GitHub.