{"record":{"id":"b7301553f2a0deda","repo":"instructure/canvas-lms","slug":"artifact-required-for-assessing","errorCode":null,"errorMessage":"Artifact required for assessing","messagePattern":"Artifact required for assessing","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/rubric_association.rb","lineNumber":310,"sourceCode":"\n  def assessment_points(criterion, data)\n    if criterion.learning_outcome_id && !context.feature_enabled?(:outcome_extra_credit)\n      [criterion.points, data[:points].to_f].min\n    else\n      data[:points].to_f\n    end\n  end\n  protected :assessment_points\n\n  def assess(opts = {})\n    # TODO: what if this is for a group assignment?  Seems like it should\n    # give all students for the group assignment the same rubric assessment\n    # results.\n    association = self\n    params = opts[:assessment]\n    raise \"User required for assessing\" unless opts[:user]\n    raise \"Assessor required for assessing\" unless opts[:assessor]\n    raise \"Artifact required for assessing\" unless opts[:artifact]\n    raise \"Assessment type required for assessing\" unless params[:assessment_type]\n\n    if opts[:artifact].is_a?(Quizzes::QuizSubmission)\n      opts[:artifact] = association_object.find_or_create_submission(opts[:artifact].user)\n    end\n\n    if association_object.is_a?(Assignment) && !association_object.grade_group_students_individually\n      group, students_to_assess = association_object.group_students(opts[:artifact].student)\n      if group\n        provisional_grader = opts[:artifact].is_a?(ModeratedGrading::ProvisionalGrade) && opts[:assessor]\n        artifacts_to_assess = students_to_assess.map do |student|\n          association_object.find_asset_for_assessment(self, student, provisional_grader:).first\n        end\n      else\n        artifacts_to_assess = [opts[:artifact]]\n      end\n    else\n      artifacts_to_assess = [opts[:artifact]]","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/rubric_association.rb#L292-L328","documentation":"RubricAssociation#assess requires an :artifact — the object being assessed (e.g. a Submission or QuizSubmission). This guard fires when opts[:artifact] is nil/absent, so Canvas has nothing to attach the rubric assessment to. It is a fail-fast contract check before the artifact is normalized or assessment rows are created.","triggerScenarios":"Calling rubric_association.assess(user:, assessor:, assessment: {...}) without opts[:artifact]; artifact lookup returning nil before the call (e.g. Submission.find_by(...) found nothing); passing an artifact key with the wrong name so opts[:artifact] stays nil.","commonSituations":"Scripts automating rubric grading where the submission lookup failed silently; LTI/tool integrations constructing the assess params themselves; quiz-submission flows where the artifact was expected to be created later but assess is called first.","solutions":["Pass the artifact explicitly: ra.assess(user: student, assessor: assessor, artifact: submission, assessment: {assessment_type: 'grading'})","Resolve the Submission first and bail out early if nil: submission = assignment.submissions.find_by(user: student) or handle absence before assessing","For quizzes, you may pass the Quizzes::QuizSubmission itself — assess converts it internally — but it must not be nil"],"exampleFix":"// before\nsubmission = assignment.submissions.find_by(user_id: student.id) # may be nil\nrubric_association.assess(user: student, assessor: assessor, artifact: submission, ...)\n// after\nsubmission = assignment.submissions.find_by!(user_id: student.id)\nraise 'submission not found' unless submission\nrubric_association.assess(user: student, assessor: assessor, artifact: submission, ...)","handlingStrategy":"validation","validationCode":"raise 'artifact required' if opts[:artifact].nil?\nrubric_association.assess(user:, assessor:, artifact: submission, assessment:)","typeGuard":"valid = opts[:artifact].is_a?(Submission) || opts[:artifact].is_a?(Quizzes::QuizSubmission)\n","tryCatchPattern":"begin\n  rubric_association.assess(opts)\nrescue RuntimeError => e\n  handle_missing_artifact if e.message == 'Artifact required for assessing'\nend","preventionTips":["Resolve the Submission with find_by! so nil lookups fail early","Pass the QuizSubmission directly for quizzes; assess normalizes it","Unit-test assess calls with all four required params"],"tags":["rubric","missing-argument","ruby"],"backgroundTag":"missing-required-argument","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}