paperclipai/paperclip · error · PhotonAnswerValidationError

Choose valid option numbers without duplicates

Error message

Choose valid option numbers without duplicates

What it means

PhotonAnswerValidationError thrown when the answer is parsed as option numbers but fails validation: numbers contain duplicates or fall outside the 1..questions.options.length range. Numeric answers are only interpreted as option selections when the question has at least one non-freeText option.

Solutions

  1. Resubmit with valid, distinct numbers between 1 and question.options.length
  2. Refetch the current interaction to see the live option list before answering
  3. If the intent is free text, disable numeric interpretation by writing non-numeric text (subject to allowOther/freeText rules)

Example fix

// before
await answer(interaction, index, "1 1 9");
// after
const nums = [1, 3];
if (new Set(nums).size !== nums.length || nums.some(n => n < 1 || n > question.options.length)) return;
await answer(interaction, index, nums.join(" "));
Defensive patterns

Strategy: validation

Validate before calling

const nums = value.split(/[ ,]+/).map(Number); const ok = nums.every(n => n >= 1 && n <= question.options.length) && new Set(nums).size === nums.length;

Try / catch

try { return parsePhotonQuestionAnswer(interaction, i, value); } catch (e) { if (e instanceof PhotonAnswerValidationError && /valid option numbers/.test(e.message)) return re-renderOptions(question); throw e; }

Prevention

When it happens

Trigger: Calling parsePhotonQuestionAnswer with numeric input like "3 3" (duplicate), "0" or "7" (out of range for the option count), for a question with non-freeText options.

Common situations: User types an option number that does not exist because the prompt was republished with fewer options; duplicated numbers from copy-paste; 0-based vs 1-based numbering confusion.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/06719972b2d629b2. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/photon/interactions.ts:103

    if (question.required !== false)
      throw new PhotonAnswerValidationError("This question requires an answer");
    return { questionId: question.id, optionIds: [] };
  }
  if (!value || value.length > 100_000)
    throw new PhotonAnswerValidationError(
      "Enter a nonempty answer within the task’s length limit",
    );
  const numbers =
    /^\d+(?:[ ,]+\d+)*$/.test(value) &&
    question.options.some((option) => !option.freeText)
      ? value.split(/[ ,]+/).map(Number)
      : null;
  if (numbers) {
    if (
      new Set(numbers).size !== numbers.length ||
      numbers.some((index) => index < 1 || index > question.options.length)
    )
      throw new PhotonAnswerValidationError(
        "Choose valid option numbers without duplicates",
      );
    if (question.selectionMode === "single" && numbers.length !== 1)
      throw new PhotonAnswerValidationError("Choose one option");
    if (numbers.some((index) => question.options[index - 1].freeText))
      throw new PhotonAnswerValidationError("Write your custom answer as text");
    return {
      questionId: question.id,
      optionIds: numbers.map((index) => question.options[index - 1].id),
    };
  }
  const freeText = question.options.find((option) => option.freeText);
  if (question.options.length && question.allowOther === false && !freeText)
    throw new PhotonAnswerValidationError(
      "Answer with the option number(s) shown in the prompt",
    );
  return {
    questionId: question.id,

View on GitHub (pinned to 3f1d897a7c)