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

plan_approval_required

Error message

plan_approval_required

What it means

Thrown when governance requires plan approval and the task is marked requires_code_change === true, but isTaskApprovedForExecution reports the task has not been approved. The gate prevents unreviewed code-changing work from being dispatched.

Source

Thrown at src/team/runtime.ts:4700

  const sanitized = sanitizeTeamName(teamName);
  return await withTeamTaskMembershipBarrier(sanitized, cwd, async () => {
    const phaseState = await readTeamPhaseState(sanitized, cwd);
    if (phaseState?.terminal_epoch || (phaseState && isTerminalPhase(phaseState.current_phase))) {
      throw new Error(teamContinuationRequiredDiagnostic(phaseState));
    }
    const task = await readTask(sanitized, taskId, cwd);
    if (!task) throw new Error(`Task ${taskId} not found`);
  const manifest = await readTeamManifestV2(sanitized, cwd);
  const governance = resolveGovernancePolicy(manifest?.governance);

  if (governance.delegation_only && workerName === 'leader-fixed') {
    throw new Error('delegation_only_violation');
  }

  if (governance.plan_approval_required && task.requires_code_change === true) {
    const approved = await isTaskApprovedForExecution(sanitized, taskId, cwd);
    if (!approved) {
      throw new Error('plan_approval_required');
    }
  }
  const config = await readTeamConfig(sanitized, cwd);
  if (!config) throw new Error(`Team ${sanitized} not found`);
  const workerInfo = config.workers.find(w => w.name === workerName);
  if (!workerInfo) throw new Error(`Worker ${workerName} not found in team`);
  const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);

  const claim = await claimTask(sanitized, taskId, workerName, task.version ?? 1, cwd);
  if (!claim.ok) {
    if (claim.error === 'blocked_dependency') {
      throw new Error(`blocked_dependency:${(claim.dependencies ?? []).join(',')}`);
    }
    throw new Error(claim.error);
  }

  try {
    // Retry dispatch up to 2 times to handle trust prompts during assignment (fixes #393).

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Approve the task plan first via the team's approval flow, then re-dispatch
  2. If approval genuinely is not needed for this task, clear requires_code_change on the task definition
  3. If governance is too strict, adjust plan_approval_required in the manifest governance policy

Example fix

// before
await assignTask(team, taskId, worker, cwd);

// after
if (task.requires_code_change) {
  await approveTaskForExecution(team, taskId, cwd); // or CLI approval command
}
await assignTask(team, taskId, worker, cwd);
Defensive patterns

Strategy: validation

Validate before calling

if (task.requires_code_change && governance.plan_approval_required) {
  const ok = await isTaskApprovedForExecution(teamName, taskId, cwd);
  if (!ok) throw new Error('approve the plan before dispatch');
}

Try / catch

try { await assignTask(t, id, w, cwd); } catch (e) { if ((e as Error).message === 'plan_approval_required') { await approveTask(t, id); await assignTask(t, id, w, cwd); } else throw e; }

Prevention

When it happens

Trigger: Assigning a requires_code_change task under a manifest whose governance sets plan_approval_required, when no approval record exists for the task (isTaskApprovedForExecution returns false).

Common situations: Automated pipelines dispatching tasks straight after creation without the approval step; approvals stored per-team that were lost when state was reset; governance defaults changing between versions so previously-fine dispatch now requires approval.

Related errors


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