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

sandbox.md frontmatter evaluator.keep_policy must be one of:

Error message

sandbox.md frontmatter evaluator.keep_policy must be one of: score_improvement, pass_only.

What it means

parseKeepPolicy throws when evaluator.keep_policy is a non-empty string but not one of the two supported values after trimming/lowercasing: 'pass_only' or 'score_improvement'. The v1 contract only defines these two retention policies.

Source

Thrown at src/autoresearch/contracts.ts:144

    }

    result[key] = value;
    currentSection = null;
  }

  return result;
}

function parseKeepPolicy(raw: unknown): AutoresearchKeepPolicy | undefined {
  if (raw === undefined) return undefined;
  if (typeof raw !== 'string') {
    throw contractError('sandbox.md frontmatter evaluator.keep_policy must be a string when provided.');
  }
  const normalized = raw.trim().toLowerCase();
  if (!normalized) return undefined;
  if (normalized === 'pass_only') return 'pass_only';
  if (normalized === 'score_improvement') return 'score_improvement';
  throw contractError('sandbox.md frontmatter evaluator.keep_policy must be one of: score_improvement, pass_only.');
}

export function parseSandboxContract(content: string): ParsedSandboxContract {
  const { frontmatter, body } = extractFrontmatter(content);
  const parsedFrontmatter = parseSimpleYamlFrontmatter(frontmatter);
  const evaluatorRaw = parsedFrontmatter.evaluator;

  if (!evaluatorRaw || typeof evaluatorRaw !== 'object' || Array.isArray(evaluatorRaw)) {
    throw contractError(EVALUATOR_BLOCK_ERROR);
  }

  const evaluator = evaluatorRaw as { command?: unknown; format?: unknown; keep_policy?: unknown };
  const command = typeof evaluator.command === 'string'
    ? evaluator.command.trim()
    : '';
  const format = typeof evaluator.format === 'string'
    ? evaluator.format.trim().toLowerCase()
    : '';

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Use exactly 'keep_policy: score_improvement' or 'keep_policy: pass_only' (snake_case, case-insensitive).
  2. Delete the key to fall back to the default behavior.
  3. Double-check for hyphens vs underscores and stray characters.

Example fix

# before
evaluator:
  keep_policy: pass-only

# after
evaluator:
  keep_policy: pass_only
Defensive patterns

Strategy: validation

Validate before calling

const VALID_KEEP_POLICIES = new Set(['pass_only', 'score_improvement']);

function keepPolicyIsValid(raw: unknown): boolean {
  return raw === undefined || (typeof raw === 'string' && (raw.trim() === '' || VALID_KEEP_POLICIES.has(raw.trim().toLowerCase())));
}

Type guard

const isKeepPolicy = (v: unknown): v is 'pass_only' | 'score_improvement' =>
  v === 'pass_only' || v === 'score_improvement';

Try / catch

try {
  parseSandboxContract(content);
} catch (err) {
  if ((err as Error).message.includes('keep_policy must be one of')) {
    // correct to pass_only or score_improvement (snake_case)
  }
  throw err;
}

Prevention

When it happens

Trigger: Frontmatter with 'keep_policy: always', 'keep_policy: none', or a typo like 'pass-only' or 'passonly' — normalized string matches neither accepted constant.

Common situations: Typos in policy names; using kebab-case instead of snake_case; guessing policy names from other tools ('retain', 'keep-all').

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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