instructure/canvas-lms · error · GraphQL::ExecutionError

not found

Error message

not found

What it means

SelectProvisionalGrade raises "not found" when the active Assignment for assignment_id either does not exist or, more specifically, when `assignment.permits_moderation?(current_user)` returns false. Although the message says "not found", it is raised deliberately (not via rescue) as a GraphQL::ExecutionError to hide the existence of assignments the user may not moderate. The generic message is intentional so unauthorized users cannot probe assignment ids.

Solutions

  1. Confirm the current user is a final grader with 'Select final grade' moderation permission on that assignment before calling the mutation.
  2. Verify the assignment_id refers to an active (non-deleted, non-soft-deleted) assignment in the same course/shard.
  3. Check assignment state: moderated grading must be enabled and grades must not yet be published.
  4. If the message should distinguish permission vs missing records, add an explicit exists check plus a permission check with distinct messages server-side.

Example fix

// before (opaque failure)
selectProvisionalGrade(input: { assignmentId: "99", provisionalGradeId: "5" })
// after: verify moderation access first in a query
query {
  assignment(id: "99") {
    permissions { selectFinalGrade }
    moderatedGrading { gradesPublished graderCount }
  }
}
// then call the mutation as an authorized final grader
Defensive patterns

Strategy: validation

Validate before calling

const perms = await client.query({ query: ASSIGNMENT_PERMS, variables: { id: assignmentId } });
const mg = perms?.data?.assignment?.moderatedGrading;
if (!perms?.data?.assignment) throw new Error('assignment not found');
if (mg?.gradesPublished) throw new Error('grades already published');
if (!perms.data.assignment.permissions?.selectFinalGrade) throw new Error('no moderation permission');

Type guard

function canModerate(assignment, userId) { return !!assignment && assignment.moderatedGrading?.finalGraderId != null && assignment.permissions?.selectFinalGrade === true; }

Try / catch

try {
  await client.mutate({ mutation: SELECT_PROVISIONAL_GRADE, variables });
} catch (e) {
  if (e.graphQLErrors?.some(g => g.message === 'not found')) {
    // re-check moderation rights and assignment existence; refresh UI state
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling selectProvisionalGrade with an assignment_id that is not an active assignment, or where the current_user is not a final grader / moderator on the assignment (permits_moderation? false), or the assignment has no moderation set / grades already published.

Common situations: A teacher whose moderation rights were removed after the UI was rendered; calling the mutation before being added as a final grader; using a deleted or unpublished assignment id; a student or observer id token attempting moderation; per-course permission changes after grades were published.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at app/graphql/mutations/select_provisional_grade.rb:32

# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class Mutations::SelectProvisionalGrade < Mutations::BaseMutation
  argument :assignment_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("Assignment")
  argument :provisional_grade_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("ModeratedGrading::ProvisionalGrade")

  field :provisional_grade, Types::ProvisionalGradeType, null: true

  def resolve(input:) # rubocop:disable GraphQL/UnusedArgument
    assignment_id = input[:assignment_id]
    provisional_grade_id = input[:provisional_grade_id]

    assignment = Assignment.active.find(assignment_id)
    raise GraphQL::ExecutionError, "not found" unless assignment.permits_moderation?(current_user)

    provisional_grade = assignment.provisional_grades.find(provisional_grade_id)
    student = provisional_grade.submission.user
    selection = ModeratedGrading::Selection.find_or_create_by!(assignment:, student:) do |s|
      s.selected_provisional_grade_id = provisional_grade.id
    end
    selection.update!(selected_provisional_grade_id: provisional_grade.id) unless selection.selected_provisional_grade_id == provisional_grade.id

    selection.create_moderation_event(current_user)

    { provisional_grade: }
  rescue ActiveRecord::RecordNotFound
    raise GraphQL::ExecutionError, "not found"
  end
end

View on GitHub (pinned to 1c9f0bb801)