Yeachan-Heo/oh-my-codex · error · Error
visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STAT
Error message
visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STATUSES.join('|')} What it means
parseVisualVerdictStatus requires visual_verdict.verdict to be a string that, after trim+lowercase, is one of VISUAL_VERDICT_STATUSES. It throws the same message both for non-string values and for unrecognized status strings, listing the allowed statuses in the message.
Source
Thrown at src/visual/verdict.ts:38
}
function asTrimmedStringArray(value: unknown, field: string): string[] {
if (!Array.isArray(value)) {
throw new Error(`visual_verdict.${field} must be an array`);
}
return value
.map((item) => {
if (typeof item !== 'string') {
throw new Error(`visual_verdict.${field} must contain strings`);
}
return item.trim();
})
.filter((item) => item.length > 0);
}
function parseVisualVerdictStatus(value: unknown): VisualVerdictStatus {
if (typeof value !== 'string') {
throw new Error(`visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STATUSES.join('|')}`);
}
const normalized = value.trim().toLowerCase();
if (!VISUAL_VERDICT_STATUSES.includes(normalized as VisualVerdictStatus)) {
throw new Error(`visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STATUSES.join('|')}`);
}
return normalized as VisualVerdictStatus;
}
export function parseVisualVerdict(input: unknown): VisualVerdict {
if (!input || typeof input !== 'object') {
throw new Error('visual_verdict must be an object');
}
const raw = input as Record<string, unknown>;
if (typeof raw.score !== 'number' || !Number.isInteger(raw.score) || raw.score < 0 || raw.score > 100) {
throw new Error('visual_verdict.score must be an integer between 0 and 100');
}
if (typeof raw.category_match !== 'boolean') {
throw new Error('visual_verdict.category_match must be a boolean');View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Use exactly one of the allowed statuses listed in the error message (the message enumerates them)
- Normalize model output by mapping synonyms to the canonical set before parsing
- If a new status is genuinely needed, update VISUAL_VERDICT_STATUSES and re-publish both sides
Example fix
// before
{ "verdict": "FAILED", "observations": [], "next_actions": [] }
// after
{ "verdict": "fail", "observations": [], "next_actions": [] } // using an allowed status from the error message Defensive patterns
Strategy: validation
Validate before calling
const v = String(verdict.verdict ?? '').trim().toLowerCase(); if (!VISUAL_VERDICT_STATUSES.includes(v)) verdict.verdict = mapSynonym(v) ?? 'needs_review';
Type guard
const isVisualVerdictStatus = (v: unknown): v is VisualVerdictStatus => typeof v === 'string' && VISUAL_VERDICT_STATUSES.includes(v.trim().toLowerCase() as VisualVerdictStatus);
Prevention
- Constrain LLM prompts to the exact allowed verdict enum
- Map model synonyms to canonical statuses before parsing
When it happens
Trigger: Calling parseVisualVerdict with verdict missing/null/object, or a value like 'Fail ' that normalizes fine but 'FAILED', 'pass_with_issues', or any status not in VISUAL_VERDICT_STATUSES.
Common situations: LLM agents inventing new verdict labels; case mismatches handled by normalization but synonym mismatches not (e.g. 'needs_work' vs the allowed value); upstream schema versions that renamed statuses.
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
- catalog_manifest_invalid:skills[${index}].category
- catalog_manifest_invalid:skills[${index}].status
- catalog_manifest_invalid:agents[${index}].category
- catalog_manifest_invalid:agents[${index}].status
- `target.type` must be "session" or "pane"
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/ed6311ca96ee4de3.
Report an issue: GitHub.