instructure/canvas-lms · error · RuntimeError
Could not find valid answer
Error message
Could not find valid answer
What it means
Quizzes::QuizOutcomeResultBuilder#create_outcome_question_result raises when the student's submission_data contains no answer for the cached question (matched by question_id). The submission snapshot has the question but no recorded answer record, so an outcome result for that question cannot be built.
Solutions
- Inspect the quiz submission's submission_data to see which question_ids are present
- Confirm the student actually answered that question; skip outcome result generation for unanswered questions
- Guard with a conditional skip rather than raise when cached_answer is nil
- Re-generate submission_data (regrade/rebuild snapshot) if it is corrupted relative to quiz_data
Example fix
// before
raise "Could not find valid answer" unless cached_answer
// after
cached_answer = @qs.submission_data.detect { |q| q[:question_id] == cached_question[:id] }
next unless cached_answer
Defensive patterns
Strategy: validation
Validate before calling
next unless @qs.quiz_data&.any? { |q| q[:assessment_question_id] == question.id } &&
@qs.submission_data.to_a.any? { |a| a[:question_id].to_s == cached_question[:id].to_s } Type guard
has_answer = ->(qs, qid) { qs.submission_data.to_a.any? { |a| a[:question_id].to_s == qid.to_s } } Try / catch
begin
builder.build_outcome_results(alignment, questions)
rescue RuntimeError => e
Rails.logger.warn("skipping question without answer: #{e.message}")
end Prevention
- Treat unanswered questions as zero-score skips rather than hard failures
- Normalize question_id comparisons (to_s) between quiz_data and submission_data
- Regenerate/rebuild submission snapshots if they drift from quiz_data
- Exclude never-answered questions from outcome result generation upfront
When it happens
Trigger: @qs.submission_data lacks an entry whose :question_id equals cached_question[:id] — e.g. the question was added to quiz_data after submission, answer data was pruned, or an unanswered/skipped question produced no submission_data entry.
Common situations: Student never answered the question and no placeholder answer record exists; quiz_data and submission_data versions are out of sync; external/essay questions migrated leaving inconsistent snapshots; data cleanup jobs removed submission_data entries.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Could not find valid question
- Cannot archive a deleted LearningOutcome
- Cannot archive a deleted LearningOutcomeGroup
- Cannot modify outcome from another context
- Cannot set rubric self assessment for quiz assignments
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/8dc7df6d41556baa.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/quizzes/quiz_outcome_result_builder.rb:58
def create_outcome_question_result(question, alignment)
# find or create the user's unique LearningOutcomeResult for this alignment
# of the quiz question.
quiz_result = alignment.learning_outcome_results
.for_association(@qs.quiz)
.for_associated_asset(@qs.quiz)
.where(user_id: @qs.user.id)
.first_or_initialize
quiz_result.workflow_state = :active
quiz_result.user_uuid = @qs.user.uuid
# get data from quiz submission's question result to ensure result should be generated
cached_question = @qs.quiz_data.detect { |q| q[:assessment_question_id] == question.id }
raise "Could not find valid question" unless cached_question
cached_answer = @qs.submission_data.detect { |q| q[:question_id] == cached_question[:id] }
raise "Could not find valid answer" unless cached_answer
# Create a question scoped outcome result linked to the quiz_result.
question_result = quiz_result.learning_outcome_question_results.for_associated_asset(question).first_or_initialize
# do not create a result if no points are possible.
if cached_question["points_possible"] == 0
# destroy any existing results that might be persisted if points possible were not always 0
question_result.destroy if question_result.persisted?
return
end
# update the result with stuff from the quiz submission's question result
question_result.learning_outcome = quiz_result.learning_outcome
# mastery
question_result.score = cached_answer[:points]
question_result.possible = cached_question["points_possible"]
question_result.calculate_percent!View on GitHub (pinned to 1c9f0bb801)