{"record":{"id":"a57a6e39a087e7cf","repo":"Yeachan-Heo/oh-my-codex","slug":"answers-index-answer-kind-must-be-option-othe","errorCode":null,"errorMessage":"answers[${index}].answer.kind must be option, other, or multi","messagePattern":"answers\\[(.+?)\\]\\.answer\\.kind must be option, other, or multi","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/question/events.ts","lineNumber":226,"sourceCode":"        : [];\n\n  if (rawAnswers.length === 0) throw new Error('answer payload must include answer or answers[]');\n  const validQuestionIds = new Set((record.questions ?? []).map((question) => question.id));\n  if (validQuestionIds.size === 0) validQuestionIds.add('q-1');\n  const seen = new Set<string>();\n\n  return rawAnswers.map((entry, index) => {\n    if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error(`answers[${index}] must be an object`);\n    const object = entry as Record<string, unknown>;\n    const questionId = safeString(object.question_id) || (record.questions?.[index]?.id ?? (index === 0 ? 'q-1' : ''));\n    if (!questionId || !validQuestionIds.has(questionId)) throw new Error(`answers[${index}].question_id is unknown for this question: ${questionId || '<missing>'}`);\n    if (seen.has(questionId)) throw new Error(`answers question_id must be unique: ${questionId}`);\n    seen.add(questionId);\n    const answer = object.answer;\n    if (!answer || typeof answer !== 'object' || Array.isArray(answer)) throw new Error(`answers[${index}].answer must be an object`);\n    const answerObject = answer as Record<string, unknown>;\n    const kind = safeString(answerObject.kind);\n    if (!['option', 'other', 'multi'].includes(kind)) throw new Error(`answers[${index}].answer.kind must be option, other, or multi`);\n    if (!Array.isArray(answerObject.selected_labels) || !Array.isArray(answerObject.selected_values)) {\n      throw new Error(`answers[${index}].answer must include selected_labels[] and selected_values[]`);\n    }\n    return {\n      question_id: questionId,\n      index: Number.isInteger(object.index) ? object.index as number : index,\n      answer: answerObject as unknown as QuestionAnswerEntry['answer'],\n    };\n  });\n}\n","sourceCodeStart":208,"sourceCodeEnd":237,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/question/events.ts#L208-L237","documentation":"Thrown by normalizeSubmittedAnswers when validating an entry in the answers[] array of a question submission. Each answer object's answer.kind field must be the string 'option', 'other', or 'multi' (after safeString coercion); any other value (undefined, number, null, arbitrary string) fails. This enforces the discriminated answer shape before the submission is persisted.","triggerScenarios":"Calling the question result/submission API with answers[i].answer missing kind, or with kind set to something like 'text', 'choice', 42, or undefined. safeString coerces non-strings to a sentinel, so only exactly 'option' | 'other' | 'multi' pass.","commonSituations":"Client serializing its own answer shape (e.g. {answer: {value: ...}}) instead of the OMX answer schema; version drift where older clients used a different kind enum; hand-built JSON payloads in tests or scripts.","solutions":["Set answers[i].answer.kind to one of 'option', 'other', or 'multi' exactly (lowercase).","Ensure kind is a string primitive, not a String object or a number; safeString only accepts real strings.","Validate the whole payload against the expected QuestionAnswerEntry shape before calling result/normalizeSubmittedAnswers."],"exampleFix":"// before\nanswers: [{ question_id: 'q1', answer: { text: 'hello' } }]\n\n// after\nanswers: [{ question_id: 'q1', answer: { kind: 'other', selected_labels: ['Other'], selected_values: ['hello'] } }]","handlingStrategy":"validation","validationCode":"function isValidAnswerKind(a: unknown): boolean {\n  const kind = (a as any)?.kind;\n  return typeof kind === 'string' && ['option','other','multi'].includes(kind);\n}\nconst ok = answers.every((e, i) => isValidAnswerKind(e?.answer) || (console.error(`answers[${i}].answer.kind invalid`), false));","typeGuard":"function isQuestionAnswerKind(v: unknown): v is 'option' | 'other' | 'multi' {\n  return v === 'option' || v === 'other' || v === 'multi';\n}","tryCatchPattern":"try { await result({ answers }); } catch (e) { if (e instanceof Error && /answer\\.kind must be option/.test(e.message)) { /* fix payload at index parsed from message */ } else throw e; }","preventionTips":["Build answers with a typed factory that hardcodes kind to the literal union.","Run normalizeSubmittedAnswers on sample payloads in unit tests.","Keep a shared zod schema for QuestionAnswerEntry on both producer and consumer sides."],"tags":["schema-validation","answers","question-flow"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}