instructure/canvas-lms · error · Assignment::GradeError
User not authorized to give final provisional grades
Error message
User not authorized to give final provisional grades
What it means
Submission#find_or_create_provisional_grade! creates or updates a ModeratedGrading::ProvisionalGrade for a scorer. When attrs[:final] is true, the scorer must hold moderation permission on the assignment (assignment.permits_moderation?); otherwise Assignment::GradeError 'User not authorized to give final provisional grades' is raised. Final provisional grades are reserved for the assignment's moderator(s).
Solutions
- Ensure the scorer has the moderator role / 'Select final grade' permission on the assignment before passing final: true.
- Call with final: false for provisional (non-final) grades from non-moderator graders.
- Check assignment.permits_moderation?(scorer) client-side or in the controller and return a 401/403 before invoking the method.
- If the user should be a moderator, grant them the appropriate role (e.g. add as moderator on the moderated assignment / enroll with grading permissions).
Example fix
// before
submission.find_or_create_provisional_grade!(scorer, { final: true, score: 92 })
# => Assignment::GradeError for non-moderator
// after
if assignment.permits_moderation?(scorer)
submission.find_or_create_provisional_grade!(scorer, { final: true, score: 92 })
else
submission.find_or_create_provisional_grade!(scorer, { final: false, score: 92 })
end Defensive patterns
Strategy: validation
Validate before calling
unless assignment.permits_moderation?(scorer)
return render json: { error: 'not authorized for final provisional grades' }, status: :forbidden
end Type guard
def can_give_final_provisional_grade?(assignment, user) assignment.permits_moderation?(user) end
Try / catch
begin
submission.find_or_create_provisional_grade!(scorer, final: true, score: score)
rescue Assignment::GradeError => e
render json: { error: e.message }, status: :unauthorized
end Prevention
- Only pass final: true for users with moderation rights on the assignment.
- Check assignment.permits_moderation?(scorer) before calling provisional-grade APIs.
- Provisional graders should always use final: false.
When it happens
Trigger: Call submission.find_or_create_provisional_grade!(scorer, { final: true, ... }) where scorer is a TA/provisional grader without moderation rights (not the final grader / moderator, no 'Moderate Grades' permission on the course/assignment).
Common situations: API or UI request from a provisional grader (non-moderator TA) attempting to post a final grade during moderated grading; custom integrations passing final: true for any scorer; permission changes removing the moderator role after grades were being posted.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- insufficient permission
- insufficient permission
- insufficient permission
- insufficient permission
- Insufficient permission
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/845c58acf698014f.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/submission.rb:2550
pgs.detect { |pg2| !pg2.final && pg2.scorer_id == scorer.id }
end
elsif final
provisional_grades.final.first
else
provisional_grades.not_final.where(scorer_id: scorer).first
end
if default_to_null_grade && pg.nil?
ModeratedGrading::NullProvisionalGrade.new(self, scorer.id, final)
else
pg
end
end
def find_or_create_provisional_grade!(scorer, attrs = {})
ModeratedGrading::ProvisionalGrade.unique_constraint_retry do
if attrs[:final] && !assignment.permits_moderation?(scorer)
raise Assignment::GradeError, "User not authorized to give final provisional grades"
end
pg = find_existing_provisional_grade(scorer, attrs[:final]) || provisional_grades.build
pg = update_provisional_grade(pg, scorer, attrs)
pg.save! if attrs[:force_save] || pg.new_record? || pg.changed?
pg
end
end
def find_existing_provisional_grade(scorer, final)
final ? provisional_grades.final.first : provisional_grades.not_final.find_by(scorer:)
end
def moderated_grading_allow_list(current_user = user, loaded_attachments: nil)
return nil unless assignment.moderated_grading? && current_user.present?
moderation_allow_list_for_user(current_user).map(&:moderated_grading_ids)
endView on GitHub (pinned to 1c9f0bb801)