{"record":{"id":"4e707c061cb752b4","repo":"paperclipai/paperclip","slug":"question-response-interaction-not-answered","errorCode":null,"errorMessage":"question_response_interaction_not_answered","messagePattern":"question_response_interaction_not_answered","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/question-response-delivery.ts","lineNumber":162,"sourceCode":"                  ? { placeholder: customOption.description }\n                  : {}),\n              },\n            }\n          : {}),\n      };\n    }),\n  };\n}\n\nexport function buildQuestionResponseDeliveryEnvelope(\n  interaction: AskUserQuestionsInteraction,\n): QuestionResponseDeliveryEnvelope {\n  if (\n    interaction.status !== \"answered\" ||\n    !interaction.result ||\n    interaction.result.cancelled === true\n  ) {\n    throw new Error(\"question_response_interaction_not_answered\");\n  }\n  const questionSet = canonicalQuestionSet(interaction);\n  const questionById = new Map(\n    questionSet.questions.map((question) => [question.id, question]),\n  );\n  const response: PaperclipQuestionResponse = {\n    schema: \"paperclip.question_response.v1\",\n    answers: Object.fromEntries(\n      interaction.result.answers.map((answer) => {\n        const question = questionById.get(answer.questionId);\n        return [\n          answer.questionId,\n          question?.answerMode === \"text\"\n            ? { ...(answer.otherText ? { text: answer.otherText } : {}) }\n            : {\n                selectedOptionIds: answer.optionIds,\n                ...(answer.otherText ? { customText: answer.otherText } : {}),\n              },","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/question-response-delivery.ts#L144-L180","documentation":"Thrown by buildQuestionResponseDeliveryEnvelope when an ask_user_questions interaction is not in a deliverable answered state. The library requires an interaction whose status is 'answered', with a non-null result, and whose result.cancelled is not true, before it can build the durable delivery envelope. It is an internal invariant guard against attempting delivery of an unanswered or cancelled question.","triggerScenarios":"Calling buildQuestionResponseDeliveryEnvelope (directly or via envelope, formatDurableQuestionResponseSummary) with an interaction whose status !== 'answered', whose result is null/undefined, or whose result.cancelled === true.","commonSituations":"Caller resolved a question interaction id from the DB without re-checking status; a race where the interaction was cancelled between fetch and delivery; passing an interaction that only reached 'pending' or 'dismissed'; formatting a summary for an interaction that a user never answered.","solutions":["Fetch the interaction fresh and verify interaction.status === 'answered' before building the envelope","Check interaction.result is non-null and interaction.result.cancelled !== true; skip delivery for cancelled interactions","Treat the error as a skip signal: mark the delivery as not-applicable instead of retrying","If this fires unexpectedly in normal flow, check for a race between interaction resolution and delivery scheduling and re-read state"],"exampleFix":"// before\nconst envelope = buildQuestionResponseDeliveryEnvelope(interaction);\n// after\nif (interaction.status === \"answered\" && interaction.result && !interaction.result.cancelled) {\n  const envelope = buildQuestionResponseDeliveryEnvelope(interaction);\n} else {\n  logger.info({ interactionId: interaction.id }, \"question interaction not deliverable; skipping\");\n}","handlingStrategy":"validation","validationCode":"function isDeliverable(i) {\n  return i.status === \"answered\" && i.result != null && i.result.cancelled !== true;\n}\nif (!isDeliverable(interaction)) throw new SkipDeliveryError(interaction.id);","typeGuard":"function isAnsweredInteraction(i) {\n  return i.status === \"answered\" &&\n    typeof i.result === \"object\" && i.result !== null &&\n    (\"cancelled\" in i.result ? i.result.cancelled !== true : true);\n}","tryCatchPattern":"try {\n  const envelope = buildQuestionResponseDeliveryEnvelope(interaction);\n} catch (e) {\n  if (e.message === \"question_response_interaction_not_answered\") {\n    logger.warn({ interactionId: interaction.id }, \"skipping non-answered question interaction\");\n    return null;\n  }\n  throw e;\n}","preventionTips":["Re-read the interaction from the DB immediately before building the envelope to avoid stale-state races","Always check result.cancelled before scheduling delivery","Centralize the answered-state check in one helper used by all delivery paths","Write a test covering cancelled and pending interactions reaching the delivery path"],"tags":["invariant","interaction","delivery"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}