Yeachan-Heo/oh-my-codex · critical · UltragoalError

Refusing a durable ultragoal mutation before writable lifecy

Error message

Refusing a durable ultragoal mutation before writable lifecycle authority is restored: ${error.message} Restore the authoritative session binding so OMX_SESSION_ID matches the current session.json before retrying; see docs/troubleshooting.md (stale session pointer recovery).

What it means

A durable ultragoal mutation was blocked because assertUltragoalWritableLifecycleAuthority failed with either WRITABLE_STATE_SCOPE_ERRORS.unusableSession or unboundEnvironment. The wrapper converts these into an UltragoalError that explicitly refuses writes until the session binding is healed — the environment's OMX_SESSION_ID no longer matches the authoritative session.json, so writing now would corrupt ownership of durable state.

Source

Thrown at src/ultragoal/artifacts.ts:909

    };
  } catch (error) {
    // Existing-plan mutators keep the documented unbound compatibility path.
    // Bootstrap callers opt out so failed state activation cannot create a new durable plan.
    if (
      error instanceof Error
      && error.message === WRITABLE_STATE_SCOPE_ERRORS.unboundEnvironment
      && options.allowUnboundEnvironment !== false
    ) {
      return { kind: 'no-pointer-compat' };
    }
    if (
      error instanceof Error
      && (
        error.message === WRITABLE_STATE_SCOPE_ERRORS.unusableSession
        || error.message === WRITABLE_STATE_SCOPE_ERRORS.unboundEnvironment
      )
    ) {
      throw new UltragoalError(
        `Refusing a durable ultragoal mutation before writable lifecycle authority is restored: ${error.message} Restore the authoritative session binding so OMX_SESSION_ID matches the current session.json before retrying; see docs/troubleshooting.md (stale session pointer recovery).`,
      );
    }
    throw error;
  }
}

function writableAuthorityEquals(
  beforeLock: UltragoalWritableAuthority,
  afterLock: UltragoalWritableAuthority,
): boolean {
  if (beforeLock.kind !== afterLock.kind) return false;
  if (beforeLock.kind === 'no-pointer-compat') return true;
  const resolvedAfterLock = afterLock as Extract<UltragoalWritableAuthority, { kind: 'resolved' }>;
  return beforeLock.source === resolvedAfterLock.source
    && beforeLock.sessionId === resolvedAfterLock.sessionId
    && beforeLock.stateDir === resolvedAfterLock.stateDir;
}

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Refresh the session binding so OMX_SESSION_ID equals the id in the current session.json (re-source the session env or restart from the launching shell)
  2. If the old session is the correct authority, restore session.json to point at it instead of exporting a new id
  3. Unset OMX_SESSION_ID if this context should be unbound-but-authorized, if your scope supports that mode
  4. See docs/troubleshooting.md, section 'stale session pointer recovery', as the message itself directs

Example fix

# before
export OMX_SESSION_ID=old-session-id # stale
omx ultragoal append --goal 'Fix::Thing' # refuses

# after
export OMX_SESSION_ID=$(jq -r .sessionId .omx/session.json)
omx ultragoal append --goal 'Fix::Thing'
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';

function sessionBindingIsCurrent(repoRoot: string): boolean {
  const envId = process.env.OMX_SESSION_ID;
  if (!envId) return false; // unboundEnvironment
  try {
    const session = JSON.parse(readFileSync(`${repoRoot}/.omx/session.json`, 'utf8'));
    return session.sessionId === envId;
  } catch {
    return false;
  }
}

Type guard

function isLifecycleAuthorityError(e: unknown): boolean {
  return e instanceof UltragoalError && e.message.includes('writable lifecycle authority is restored');
}

Try / catch

try {
  await mutateUltragoalState(...);
} catch (e) {
  if (isLifecycleAuthorityError(e)) {
    await refreshSessionBinding(); // re-export OMX_SESSION_ID from session.json, then retry once
    return mutateUltragoalState(...);
  }
  throw e;
}

Prevention

When it happens

Trigger: Any durable ultragoal mutation API wrapped by this guard when OMX_SESSION_ID in the environment disagrees with the current session.json (unusableSession), or when the writable state scope has no session bound at all (unboundEnvironment) — e.g. reusing a shell from a previous session, resuming after a session restart, or exporting a stale OMX_SESSION_ID in CI.

Common situations: Long-lived terminal tabs where the session was rotated underneath; CI jobs caching environment variables between runs; scripts sourcing an old .env containing OMX_SESSION_ID; session.json rewritten by a new SessionStart while an old process still runs.

Related errors


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