Yeachan-Heo/oh-my-codex · error
answers[${index}].answer.kind must be option, other, or mult
Error message
answers[${index}].answer.kind must be option, other, or multi What it means
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.
Source
Thrown at src/question/events.ts:226
: [];
if (rawAnswers.length === 0) throw new Error('answer payload must include answer or answers[]');
const validQuestionIds = new Set((record.questions ?? []).map((question) => question.id));
if (validQuestionIds.size === 0) validQuestionIds.add('q-1');
const seen = new Set<string>();
return rawAnswers.map((entry, index) => {
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) throw new Error(`answers[${index}] must be an object`);
const object = entry as Record<string, unknown>;
const questionId = safeString(object.question_id) || (record.questions?.[index]?.id ?? (index === 0 ? 'q-1' : ''));
if (!questionId || !validQuestionIds.has(questionId)) throw new Error(`answers[${index}].question_id is unknown for this question: ${questionId || '<missing>'}`);
if (seen.has(questionId)) throw new Error(`answers question_id must be unique: ${questionId}`);
seen.add(questionId);
const answer = object.answer;
if (!answer || typeof answer !== 'object' || Array.isArray(answer)) throw new Error(`answers[${index}].answer must be an object`);
const answerObject = answer as Record<string, unknown>;
const kind = safeString(answerObject.kind);
if (!['option', 'other', 'multi'].includes(kind)) throw new Error(`answers[${index}].answer.kind must be option, other, or multi`);
if (!Array.isArray(answerObject.selected_labels) || !Array.isArray(answerObject.selected_values)) {
throw new Error(`answers[${index}].answer must include selected_labels[] and selected_values[]`);
}
return {
question_id: questionId,
index: Number.isInteger(object.index) ? object.index as number : index,
answer: answerObject as unknown as QuestionAnswerEntry['answer'],
};
});
}
View on GitHub (pinned to 3ad79a8a6f)
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.
Example fix
// before
answers: [{ question_id: 'q1', answer: { text: 'hello' } }]
// after
answers: [{ question_id: 'q1', answer: { kind: 'other', selected_labels: ['Other'], selected_values: ['hello'] } }] Defensive patterns
Strategy: validation
Validate before calling
function isValidAnswerKind(a: unknown): boolean {
const kind = (a as any)?.kind;
return typeof kind === 'string' && ['option','other','multi'].includes(kind);
}
const ok = answers.every((e, i) => isValidAnswerKind(e?.answer) || (console.error(`answers[${i}].answer.kind invalid`), false)); Type guard
function isQuestionAnswerKind(v: unknown): v is 'option' | 'other' | 'multi' {
return v === 'option' || v === 'other' || v === 'multi';
} Try / catch
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; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- answers[${index}].answer must include selected_labels[] and
- ${path} must be a non-empty string array
- selected value is not in the option schema: ${value}
- ${path} cardinality must match selected values
- ${path}[${mismatchIndex}] must match the selected value labe
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/a57a6e39a087e7cf.
Report an issue: GitHub.