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

Cannot record a blocked checkpoint for ${goal.id} while it i

Error message

Cannot record a blocked checkpoint for ${goal.id} while it is ${goal.status}; start or resume the ultragoal before recording a non-terminal blocker.

What it means

A 'blocked' checkpoint was requested for a goal whose status is not 'in_progress'. Non-terminal blockers may only be recorded against a goal that has been started or resumed; a pending/complete/failed goal cannot accept a blocked checkpoint.

Source

Thrown at src/ultragoal/artifacts.ts:1795

  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,
        evidence: options.evidence,
        codexGoal: options.codexGoal,
        message: 'Codex get_goal was unavailable due to a DB/schema/context error; strict completion reconciliation is deferred until get_goal works.',
      });
      return plan;

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Start/resume the goal first (record an in_progress checkpoint), then record the blocked checkpoint
  2. If the goal already reached a terminal state, record the appropriate terminal checkpoint instead of 'blocked'
  3. Fix workflow ordering so 'start' always precedes 'blocked' reporting
Defensive patterns

Strategy: validation

Validate before calling

const goal = plan.goals.find(g => g.id === options.goalId);
if (options.status === 'blocked' && goal.status !== 'in_progress') throw new Error('start the goal before blocking');

Type guard

const canBlock = (goal: UltragoalItem) => goal.status === 'in_progress';

Prevention

When it happens

Trigger: Calling checkpointUltragoal with status 'blocked' when goal.status is 'pending' (never started), 'complete', 'failed', or 'review_blocked'.

Common situations: Automation recording blockers before the start step ran; retrying a blocked report after the goal already failed or completed; out-of-order workflow steps.

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/b429a4c549ab887e. Report an issue: GitHub.