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

not found

Error message

not found

What it means

BaseMutation#verify_authorized_action! raises a GraphQL::ExecutionError 'not found' when obj.grants_right?(current_user, session, perm) is false. To avoid leaking existence information, any authorization failure is reported as if the resource does not exist. It is the standard authorization guard for all Canvas GraphQL mutations.

Solutions

  1. Confirm the authenticated user actually holds the required permission on the object (check enrollments/roles)
  2. Verify the GraphQL context carries a valid current_user and session (token not expired)
  3. Check the object id resolves to the intended resource in the same account/shard
  4. If the user should have access, fix the role/enrollment; otherwise change the client to use an authorized user

Example fix

// before
mutation {$input: ...} // sent as student token
// after
// use a token for a user with the required role, or pre-check:
raise GraphQL::ExecutionError, "insufficient permissions" unless course.grants_right?(user, session, :manage_grades)
Defensive patterns

Strategy: validation

Validate before calling

// client-side pre-check of the user's enrollments/permissions
const perms = await canvas.get(`/api/v1/courses/${courseId}/users/self/permissions`)
if (!perms[requiredPermission]) throw new Error(`user lacks ${requiredPermission}`)

Type guard

function isAuthorizedContext(ctx) { return ctx != null && ctx.currentUser != null && ctx.currentUser.id != null }

Try / catch

try {
  return await client.mutate({ mutation: MUTATION, variables })
} catch (e) {
  if (e.message === 'not found') {
    // treat as authorization failure: check user role/session, do not retry blindly
  }
  throw e
}

Prevention

When it happens

Trigger: Any mutation calling verify_authorized_action!(obj, :perm) where the current user lacks the permission: anonymous/unauthenticated requests, wrong enrollment type, user not in the course, or obj not visible to the user.

Common situations: A student token attempting a teacher-only mutation; expired session so current_user is nil; querying a resource in a course the user was removed from; API client using a user from a different shard/root account.

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


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

Appendix: source

Thrown at app/graphql/mutations/base_mutation.rb:55

  field :errors, [Types::ValidationErrorType], null: true

  def current_user
    context[:current_user]
  end

  def resolve_with_support(**input)
    # our resolvers generally expect a hash, not GraphQL objects, so just transform it here
    input_hash = input.deep_transform_values { |v| v.is_a?(GraphQL::Schema::InputObject) ? v.to_h : v }

    super(input: input_hash)
  end

  def session
    context[:session]
  end

  def verify_authorized_action!(obj, perm)
    raise GraphQL::ExecutionError, "not found" unless obj.grants_right?(current_user, session, perm)
  end

  def verify_any_authorized_actions!(obj, perms)
    raise GraphQL::ExecutionError, "not found" unless obj.grants_any_right?(current_user, session, *Array(perms))
  end

  # TODO: replace this with model validation where applicable
  def validation_error(message, attribute: "message")
    {
      errors: {
        attribute.to_sym => message
      }
    }
  end

  private

  # returns validation errors in a consistent format (`Types::ValidationError`)

View on GitHub (pinned to 1c9f0bb801)