instructure/canvas-lms · error · GraphQL::ExecutionError
# not found
Error message
#{e.model} not found What it means
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.
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.
Example fix
// before
mutation {
saveRubricAssessment(input: { submissionId: "12345", ... }) { ... }
}
// after
// verify id first
query { legacyNode(__typename: "Submission", id: "12345") { ... } }
// then call the mutation with a confirmed existing submission id Defensive patterns
Strategy: try-catch
Validate before calling
const exists = await client.query({ query: LEGACY_NODE_QUERY, variables: { id: submissionId } });
if (!exists?.data?.legacyNode) throw new Error(`Submission ${submissionId} not found`); Type guard
function isRecordAvailable(node) { return node != null && node._id != null; } Try / catch
try {
await client.mutate({ mutation: SAVE_RUBRIC_ASSESSMENT, variables });
} catch (e) {
if (e.graphQLErrors?.some(g => /not found$/.test(g.message))) {
// refresh ids / notify user the record no longer exists
} else { throw e; }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- not found
- A course with that id does not exist
- ActiveRecord::RecordNotFound
- Allocation rule not found
- Allocation rule not found
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/dfbe0d029b0a3624.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/mutations/save_rubric_assessment.rb:87
rubric_assessment = association.assess(
assessor: current_user,
user:,
artifact: asset,
assessment: assessment_details,
graded_anonymously: input[:graded_anonymously]
)
submission.reload
return { submission:, rubric_assessment:, rubric_association: association }
end
rescue Assignment::MaxGradersReachedError => e
raise GraphQL::ExecutionError, e.message
rescue Assignment::GradeError
raise GraphQL::ExecutionError, "Assignment Grade Error"
end
rescue ActiveRecord::RecordNotFound => e
raise GraphQL::ExecutionError, "#{e.model} not found"
end
def ensure_adjudication_possible(provisional:, association_object:, grader:, &)
# Non-assignment association objects crash if they're passed into this
# controller, since find_asset_for_assessment only exists on assignments.
# The check here thus serves only to make sure the crash doesn't happen on
# the call below.
return yield unless association_object.is_a?(Assignment)
association_object.ensure_grader_can_adjudicate(
grader:,
provisional:,
occupy_slot: true,
&
)
end
end
View on GitHub (pinned to 1c9f0bb801)