instructure/canvas-lms · error · RuntimeError

Quizzes::QuizSubmission.update_scores called on a quiz that…

Error message

Quizzes::QuizSubmission.update_scores called on a quiz that appears to be in progress

What it means

Inside update_scores, each entry of the version's submission_data is expected to be a hash (with_indifferent_access). If an entry is a scalar/string, the submission data is considered corrupt or from an in-progress attempt, so Canvas logs the submission JSON and answer, then raises this RuntimeError. It is an internal data-integrity guard.

Solutions

  1. Inspect submission_data for the targeted version and repair/normalize non-hash entries to hashes
  2. Re-grade against a completed version (pass submission_version_number) whose data is well-formed
  3. Re-save/re-grade the submission (grade_the_submission) to regenerate properly structured data
  4. Check logs (submission to_json and answer inspect are logged at error) to identify the corrupted record and fix the data
Defensive patterns

Strategy: try-catch

Validate before calling

bad = Array(version.model.submission_data).reject { |a| a.respond_to?(:with_indifferent_access) }
raise CorruptSubmissionData if bad.any?

Type guard

def valid_submission_data?(data) = data.is_a?(Array) && data.all? { |a| a.is_a?(Hash) || a.respond_to?(:with_indifferent_access) }

Try / catch

begin
  submission.update_scores(user, params)
rescue RuntimeError => e
  raise unless e.message.include?("appears to be in progress")
  flag_submission_for_repair(submission)
end

Prevention

When it happens

Trigger: submission_data for the selected version contains non-hash entries — typically an attempt still in progress was saved as raw/scalar data, or the stored version data is corrupted/legacy-shaped.

Common situations: Scoring a version whose model.submission_data was serialized mid-attempt; database rows written by older Canvas versions or interrupted saves; data migrations leaving malformed submission_data; race where the version snapshot was captured before answers became hashes.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/d1af026c8fd801ed. Report an issue: GitHub.

Appendix: source

Thrown at app/models/quizzes/quiz_submission.rb:769

    submission&.mark_unread(user)
    versions = self.versions
    version = versions.current
    version = versions.get(params[:submission_version_number]) if params[:submission_version_number]
    # NOTE: that self may not match versions.current, because we only save a new version on actual submit
    raise "Can't update submission scores unless it's completed" if !completed? && !params[:submission_version_number]

    data = version.model.submission_data || []
    res = []
    tally = 0
    completed_before_changes = completed?
    self.workflow_state = "complete"
    self.fudge_points = params[:fudge_points].to_f if params[:fudge_points] && params[:fudge_points] != ""
    tally += fudge_points if fudge_points
    data.each do |answer|
      unless answer.respond_to?(:with_indifferent_access)
        logger.error "submission = #{to_json}"
        logger.error "answer = #{answer.inspect}"
        raise "Quizzes::QuizSubmission.update_scores called on a quiz that appears to be in progress"
      end
      answer = answer.with_indifferent_access
      score = params[:"question_score_#{answer["question_id"]}"]
      answer["more_comments"] = params[:"question_comment_#{answer["question_id"]}"] if params[:"question_comment_#{answer["question_id"]}"]
      if score.present?
        begin
          float_score = score.to_f
        rescue
          float_score = nil
        end
        answer["points"] = float_score || answer["points"] || 0
        answer["correct"] = "defined" if answer["correct"] == "undefined" && float_score
      elsif score && score.empty?
        answer["points"] = 0
        answer["correct"] = "undefined"
      end
      if answer["correct"] == "undefined"
        question = quiz_data.find { |h| h[:id] == answer["question_id"] }

View on GitHub (pinned to 1c9f0bb801)