{"record":{"id":"ada248b73bc83b8c","repo":"Yeachan-Heo/oh-my-codex","slug":"visual-verdict-field-must-contain-strings","errorCode":null,"errorMessage":"visual_verdict.${field} must contain strings","messagePattern":"visual_verdict\\.(.+?) must contain strings","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/visual/verdict.ts","lineNumber":29,"sourceCode":"  differences: string[];\n  suggestions: string[];\n  reasoning: string;\n}\n\nexport interface VisualLoopFeedback extends VisualVerdict {\n  threshold: number;\n  passes_threshold: boolean;\n  next_actions: string[];\n}\n\nfunction asTrimmedStringArray(value: unknown, field: string): string[] {\n  if (!Array.isArray(value)) {\n    throw new Error(`visual_verdict.${field} must be an array`);\n  }\n  return value\n    .map((item) => {\n      if (typeof item !== 'string') {\n        throw new Error(`visual_verdict.${field} must contain strings`);\n      }\n      return item.trim();\n    })\n    .filter((item) => item.length > 0);\n}\n\nfunction parseVisualVerdictStatus(value: unknown): VisualVerdictStatus {\n  if (typeof value !== 'string') {\n    throw new Error(`visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STATUSES.join('|')}`);\n  }\n  const normalized = value.trim().toLowerCase();\n  if (!VISUAL_VERDICT_STATUSES.includes(normalized as VisualVerdictStatus)) {\n    throw new Error(`visual_verdict.verdict must be one of: ${VISUAL_VERDICT_STATUSES.join('|')}`);\n  }\n  return normalized as VisualVerdictStatus;\n}\n\nexport function parseVisualVerdict(input: unknown): VisualVerdict {","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/visual/verdict.ts#L11-L47","documentation":"Inside a visual_verdict string-array field, one of the array elements is not a string (number, object, null, boolean). asTrimmedStringArray throws because every element of observations/next_actions must be a string (empty strings are trimmed and filtered, but non-strings are rejected).","triggerScenarios":"Calling parseVisualVerdict where e.g. visual_verdict.next_actions is [\"fix css\", 3] or [null].","commonSituations":"LLM output mixing types into arrays (numbers as step indices); JSON produced from untyped languages; verdict fixtures with nulls for empty items.","solutions":["Coerce or drop non-string elements on the producer side before serializing","Map numeric/null entries to strings (String(item)) or omit them","Validate model output with a schema before feeding it to parseVisualVerdict"],"exampleFix":"// before\n{ \"observations\": [\"ok\", 42] }\n// after\n{ \"observations\": [\"ok\", \"42\"] }","handlingStrategy":"type-guard","validationCode":"if (!arr.every(x => typeof x === 'string')) arr = arr.map(String);","typeGuard":"const isStringArray = (v: unknown): v is string[] => Array.isArray(v) && v.every(x => typeof x === 'string');","tryCatchPattern":null,"preventionTips":["Coerce non-string entries before serialization","Reject null placeholders in list fields at the source"],"tags":["visual-verdict","schema-validation","json","type-error"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}