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

Cannot record a ${options.status} checkpoint for ${goal.id}

Error message

Cannot record a ${options.status} checkpoint for ${goal.id} after the aggregate ultragoal plan is complete; the terminal aggregate receipt is immutable.

What it means

The aggregate ultragoal plan already has aggregateCompletion.status === 'complete', and you attempted to record a non-'complete' checkpoint. The terminal aggregate receipt is treated as immutable, so no further blocked/failed/in_progress checkpoints may be recorded after aggregate completion.

Source

Thrown at src/ultragoal/artifacts.ts:1790

  next.failedAt = undefined;
  next.failureReason = undefined;
  clearGoalBlockerFields(next);
  next.updatedAt = now;
  plan.activeGoalId = next.id;
  plan.updatedAt = now;
  await writePlan(cwd, plan);
  await appendLedger(cwd, { ts: now, event: 'goal_started', goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` });
  return { plan, goal: next, resumed: false, done: false };
  });
}

export async function checkpointUltragoal(cwd: string, options: CheckpointOptions): Promise<UltragoalPlan> {
  return withUltragoalMutationLock(cwd, async () => {
  const plan = await readUltragoalPlanUnderLock(cwd);
  const goal = plan.goals.find((candidate) => candidate.id === options.goalId);
  if (!goal) throw new UltragoalError(`Unknown ultragoal id: ${options.goalId}`);
  if (plan.aggregateCompletion?.status === 'complete' && options.status !== 'complete') {
    throw new UltragoalError(`Cannot record a ${options.status} checkpoint for ${goal.id} after the aggregate ultragoal plan is complete; the terminal aggregate receipt is immutable.`);
  }
  const now = iso(options.now);
  if (options.status === 'blocked') {
    if (goal.status !== 'in_progress') {
      throw new UltragoalError(`Cannot record a blocked checkpoint for ${goal.id} while it is ${goal.status}; start or resume the ultragoal before recording a non-terminal blocker.`);
    }
    const snapshot = options.codexGoal === undefined ? null : parseCodexGoalSnapshot(options.codexGoal);
    if (snapshot?.unavailableReason === 'db_schema_context_error') {
      goal.updatedAt = now;
      goal.failureReason = assertNonEmpty(options.evidence, '--evidence');
      plan.activeGoalId = goal.id;
      plan.updatedAt = now;
      await writePlan(cwd, plan);
      await appendLedger(cwd, {
        ts: now,
        event: 'goal_blocked',
        goalId: goal.id,
        status: goal.status,

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Do not record further non-complete checkpoints — the plan is finalized
  2. If the late checkpoint is legitimately needed, it must be recorded before final completion or not at all
  3. Audit your orchestration to cancel pending checkpoint jobs once aggregate completion is written
Defensive patterns

Strategy: type-guard

Validate before calling

const plan = await readUltragoalPlan(cwd);
if (plan.aggregateCompletion?.status === 'complete' && options.status !== 'complete') skip();

Type guard

const isPlanFinalized = (plan: UltragoalPlan) => plan.aggregateCompletion?.status === 'complete';

Prevention

When it happens

Trigger: Calling checkpointUltragoal with options.status of 'blocked', 'failed', or similar after plan.aggregateCompletion.status is 'complete' and the status being recorded is not 'complete'.

Common situations: Retrying an old blocked checkpoint after the final goal completed the plan; automated retry loops firing late; concurrent pipelines recording checkpoints after finalization.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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