paperclipai/paperclip · error · Error
question_response_interaction_not_answered
Error message
question_response_interaction_not_answered
What it means
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.
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
Example fix
// before
const envelope = buildQuestionResponseDeliveryEnvelope(interaction);
// after
if (interaction.status === "answered" && interaction.result && !interaction.result.cancelled) {
const envelope = buildQuestionResponseDeliveryEnvelope(interaction);
} else {
logger.info({ interactionId: interaction.id }, "question interaction not deliverable; skipping");
} Defensive patterns
Strategy: validation
Validate before calling
function isDeliverable(i) {
return i.status === "answered" && i.result != null && i.result.cancelled !== true;
}
if (!isDeliverable(interaction)) throw new SkipDeliveryError(interaction.id); Type guard
function isAnsweredInteraction(i) {
return i.status === "answered" &&
typeof i.result === "object" && i.result !== null &&
("cancelled" in i.result ? i.result.cancelled !== true : true);
} Try / catch
try {
const envelope = buildQuestionResponseDeliveryEnvelope(interaction);
} catch (e) {
if (e.message === "question_response_interaction_not_answered") {
logger.warn({ interactionId: interaction.id }, "skipping non-answered question interaction");
return null;
}
throw e;
} Prevention
- 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
When it happens
Trigger: Calling buildQuestionResponseDeliveryEnvelope (directly or via envelope, formatDurableQuestionResponseSummary) with an interaction whose status !== 'answered', whose result is null/undefined, or whose result.cancelled === true.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ACPX verified runtime descriptor is misplaced
- : capture has no states
- Expected 99 captures, found
- Expected four documented annotations for screen
- Invalid Discord correction binding
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/4e707c061cb752b4.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/question-response-delivery.ts:162
? { placeholder: customOption.description }
: {}),
},
}
: {}),
};
}),
};
}
export function buildQuestionResponseDeliveryEnvelope(
interaction: AskUserQuestionsInteraction,
): QuestionResponseDeliveryEnvelope {
if (
interaction.status !== "answered" ||
!interaction.result ||
interaction.result.cancelled === true
) {
throw new Error("question_response_interaction_not_answered");
}
const questionSet = canonicalQuestionSet(interaction);
const questionById = new Map(
questionSet.questions.map((question) => [question.id, question]),
);
const response: PaperclipQuestionResponse = {
schema: "paperclip.question_response.v1",
answers: Object.fromEntries(
interaction.result.answers.map((answer) => {
const question = questionById.get(answer.questionId);
return [
answer.questionId,
question?.answerMode === "text"
? { ...(answer.otherText ? { text: answer.otherText } : {}) }
: {
selectedOptionIds: answer.optionIds,
...(answer.otherText ? { customText: answer.otherText } : {}),
},View on GitHub (pinned to 3f1d897a7c)