{"record":{"id":"ffcedc21698890eb","repo":"Yeachan-Heo/oh-my-codex","slug":"path-cardinality-must-match-selected-values","errorCode":null,"errorMessage":"${path} cardinality must match selected values","messagePattern":"(.+?) cardinality must match selected values","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/question/state.ts","lineNumber":331,"sourceCode":"  selectedValues: string[],\n  otherText: string | undefined,\n): string[] {\n  const optionLabelsByValue = new Map(question.options.map((option) => [option.value, option.label]));\n  return selectedValues.map((value) => {\n    const optionLabel = optionLabelsByValue.get(value);\n    if (optionLabel) return optionLabel;\n    if (question.allow_other && otherText && value === otherText) return question.other_label;\n    throw new Error(`selected value is not in the option schema: ${value}`);\n  });\n}\n\nfunction assertSelectedLabelsMatch(\n  selectedLabels: string[],\n  expectedLabels: string[],\n  path: string,\n): void {\n  if (selectedLabels.length !== expectedLabels.length) {\n    throw new Error(`${path} cardinality must match selected values`);\n  }\n  const mismatchIndex = selectedLabels.findIndex((label, index) => label !== expectedLabels[index]);\n  if (mismatchIndex !== -1) {\n    throw new Error(`${path}[${mismatchIndex}] must match the selected value label`);\n  }\n}\n\nfunction questionForAnswer(record: QuestionRecord, entry: QuestionAnswerEntry): NormalizedQuestionItem {\n  const question = (record.questions ?? []).find((item) => item.id === entry.question_id);\n  if (question) return question;\n  if (entry.question_id === 'q-1') {\n    return {\n      id: 'q-1',\n      ...(record.header ? { header: record.header } : {}),\n      question: record.question,\n      options: record.options,\n      allow_other: record.allow_other,\n      other_label: record.other_label,","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/question/state.ts#L313-L349","documentation":"assertSelectedLabelsMatch requires selected_labels to have the same length as the labels expected from selected_values. A mismatch means the parallel arrays are inconsistent — one value has no corresponding label or vice versa.","triggerScenarios":"Submitting selected_values.length !== selected_labels.length, e.g. values ['a','b'] with labels ['Option A']; dropping a label when filtering values; appending a value without its label.","commonSituations":"Independent filtering of the two arrays client-side; partial updates that mutate one array only; hand-built payloads in scripts/tests.","solutions":["Always derive selected_labels from selected_values via the question schema instead of maintaining them separately","Validate lengths match right before submit"],"exampleFix":"// before\nanswer.selected_values = ['a','b'];\nanswer.selected_labels = ['Option A'];\n// after\nanswer.selected_labels = answer.selected_values.map((v) => labelForValue(question, v));","handlingStrategy":"validation","validationCode":"if (answer.selected_labels.length !== answer.selected_values.length) {\n  answer.selected_labels = answer.selected_values.map((v) => labelFor(question, v));\n}","typeGuard":"function labelsAligned(values: string[], labels: string[]): boolean { return values.length === labels.length; }","tryCatchPattern":"try { await submit(p, answers); } catch (e) { if (/cardinality must match/.test((e as Error).message)) { rebuildLabelsFromSchema(); return submit(p, answers); } throw e; }","preventionTips":["Derive selected_labels from selected_values and the schema in one function","Never filter the arrays independently"],"tags":["validation","labels","answers"],"backgroundTag":"schema-validation-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}