{"record":{"id":"bfac0b8f0643696b","repo":"paperclipai/paperclip","slug":"discord-form-correction-is-not-current","errorCode":null,"errorMessage":"Discord form correction is not current","messagePattern":"Discord form correction is not current","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-discord-question-forms.ts","lineNumber":163,"sourceCode":"    values: Record<string, string>;\n  },\n  now = new Date(),\n): Promise<DiscordModalCorrectionResponse> {\n  const expiresAt = new Date(\n    Math.min(\n      Date.parse(input.parentExpiresAt),\n      now.getTime() + CORRECTION_TTL_MS,\n    ),\n  );\n  if (\n    !Number.isFinite(expiresAt.getTime()) ||\n    expiresAt <= now ||\n    input.modal.callbackId !== input.submitActionId ||\n    input.modal.privateMetadata !== input.submitActionId ||\n    input.modal.children.length < 1 ||\n    input.modal.children.length > 5\n  )\n    throw new Error(\"Discord form correction is not current\");\n  const values: Record<string, string> = {};\n  const messages: string[] = [\n    \"Please check your answers, then select Edit answers.\",\n  ];\n  for (const child of input.modal.children) {\n    if (child.type !== \"text_input\" && child.type !== \"select\")\n      throw new Error(\"Unsupported Discord correction field\");\n    const value = input.values[child.id];\n    const error = input.fieldErrors[child.id];\n    // Both label and error are produced by canonical form validation, never\n    // provider labels/error bodies. Opaque input IDs are not visible copy.\n    if (error) messages.push(`${child.label}: ${error}`);\n    if (typeof value !== \"string\") continue;\n    if (child.type === \"text_input\") {\n      const maximum = Math.min(child.maxLength ?? 4000, 4000);\n      values[child.id] = value.slice(0, maximum);\n      if (value.length > maximum)\n        messages.push(","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-discord-question-forms.ts#L145-L181","documentation":"retainDiscordQuestionFormCorrection validates a Discord modal-based form correction before persisting it. This error means the submitted correction is stale or structurally out of date: the binding has expired, or the modal's callback ID / private metadata no longer match the submit action ID, or the modal has an invalid number of children (Discord requires 1-5 inputs). It prevents replaying or tampering with old form corrections.","triggerScenarios":"Submitting a Discord modal whose expiresAt is in the past; a modal whose callbackId or private_metadata does not equal the submitActionId; a modal built with zero children or more than 5 children.","commonSituations":"A user takes too long to answer the modal and it expires; a Discord interaction replay/duplicate delivery re-submits an old callback ID; a developer builds a modal with 6+ text inputs, which Discord rejects structurally.","solutions":["Discard the stale draft and re-issue a fresh question form so a new modal/correction binding is generated","Check that the modal's custom_id (callbackId) and private_metadata are both set to the current submitActionId when constructing the modal","Ensure the modal has between 1 and 5 child inputs before submitting the correction","If expiry is the issue, ask the user to resubmit promptly or extend the binding's expiry window"],"exampleFix":"// before\nmodal.children = sixInputs; // 6 children\n// after\nmodal.children = sixInputs.slice(0, 5); // Discord allows max 5 inputs per modal","handlingStrategy":"validation","validationCode":"function isCorrectionCurrent(input, now) {\n  return input.expiresAt > now &&\n    input.modal.callbackId === input.submitActionId &&\n    input.modal.privateMetadata === input.submitActionId &&\n    input.modal.children.length >= 1 &&\n    input.modal.children.length <= 5;\n}\nif (!isCorrectionCurrent(input, Date.now())) return promptFreshForm();","typeGuard":"const isCurrentModal = (m) => m && typeof m.callbackId === \"string\" && m.callbackId === m.privateMetadata && Array.isArray(m.children) && m.children.length >= 1 && m.children.length <= 5;","tryCatchPattern":null,"preventionTips":["Build modals with 1-5 children and assert this in the form builder","Set callbackId and privateMetadata to the same submitActionId at construction time","Show users an expiry warning before the binding expires; re-issue forms proactively","Treat Discord interaction duplicates as idempotent by interaction id"],"tags":["discord","validation","stale-state"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}