{"record":{"id":"dfbe0d029b0a3624","repo":"instructure/canvas-lms","slug":"e-model-not-found","errorCode":null,"errorMessage":"#{e.model} not found","messagePattern":"#(.+?) not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/save_rubric_assessment.rb","lineNumber":87,"sourceCode":"\n        rubric_assessment = association.assess(\n          assessor: current_user,\n          user:,\n          artifact: asset,\n          assessment: assessment_details,\n          graded_anonymously: input[:graded_anonymously]\n        )\n\n        submission.reload\n        return { submission:, rubric_assessment:, rubric_association: association }\n      end\n    rescue Assignment::MaxGradersReachedError => e\n      raise GraphQL::ExecutionError, e.message\n    rescue Assignment::GradeError\n      raise GraphQL::ExecutionError, \"Assignment Grade Error\"\n    end\n  rescue ActiveRecord::RecordNotFound => e\n    raise GraphQL::ExecutionError, \"#{e.model} not found\"\n  end\n\n  def ensure_adjudication_possible(provisional:, association_object:, grader:, &)\n    # Non-assignment association objects crash if they're passed into this\n    # controller, since find_asset_for_assessment only exists on assignments.\n    # The check here thus serves only to make sure the crash doesn't happen on\n    # the call below.\n    return yield unless association_object.is_a?(Assignment)\n\n    association_object.ensure_grader_can_adjudicate(\n      grader:,\n      provisional:,\n      occupy_slot: true,\n      &\n    )\n  end\nend\n","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/save_rubric_assessment.rb#L69-L105","documentation":"This GraphQL mutation rescues ActiveRecord::RecordNotFound raised while loading the assignment, submission, or other records in SaveRubricAssessment, and re-raises it as a GraphQL::ExecutionError with the message \"<ModelName> not found\". The library converts Rails record lookup failures into a GraphQL top-level error so the client receives a structured error instead of an unhandled exception. `e.model` is the class name of the record that could not be found (e.g. Assignment, Submission), so the message tells you which lookup failed.","triggerScenarios":"Calling the saveRubricAssessment mutation with an assignment_id, submission_id, or provisional_grade_id that does not exist (or is not visible to the current user) causes any `find` inside resolve to raise ActiveRecord::RecordNotFound, which is re-raised here as \"Assignment not found\" / \"Submission not found\" etc.","commonSituations":"A stale client cache referencing a deleted assignment or submission; passing a peer-review/sub-assignment id instead of the parent assignment id; wrong shard/context (record exists on another root account); typos in ids copied from URLs; the record was soft-deleted so find (not find by active scope) misses it.","solutions":["Verify the id(s) sent in the mutation input exist and belong to the current course/shard; re-query the assignment via GraphQL to confirm.","Check the message's model name to see which lookup failed (Assignment vs Submission vs ProvisionalGrade) and correct that specific id.","If the record was deleted, re-select the target or recreate it before saving the rubric assessment.","If you must tolerate missing records, pre-fetch with a scoped `where(id:).first` check in the caller before invoking the mutation."],"exampleFix":"// before\nmutation {\n  saveRubricAssessment(input: { submissionId: \"12345\", ... }) { ... }\n}\n// after\n// verify id first\nquery { legacyNode(__typename: \"Submission\", id: \"12345\") { ... } }\n// then call the mutation with a confirmed existing submission id","handlingStrategy":"try-catch","validationCode":"const exists = await client.query({ query: LEGACY_NODE_QUERY, variables: { id: submissionId } });\nif (!exists?.data?.legacyNode) throw new Error(`Submission ${submissionId} not found`);","typeGuard":"function isRecordAvailable(node) { return node != null && node._id != null; }","tryCatchPattern":"try {\n  await client.mutate({ mutation: SAVE_RUBRIC_ASSESSMENT, variables });\n} catch (e) {\n  if (e.graphQLErrors?.some(g => /not found$/.test(g.message))) {\n    // refresh ids / notify user the record no longer exists\n  } else { throw e; }\n}","preventionTips":["Re-query records before mutating in long-lived sessions","Never hardcode ids copied from URLs","Handle GraphQL errors arrays rather than assuming HTTP 200 means success","Watch for shard/environment mismatches when moving between test and production"],"tags":["graphql","activerecord","record-not-found","rubric-assessment"],"backgroundTag":"record-not-found","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"}