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
- Confirm the authenticated user actually holds the required permission on the object (check enrollments/roles)
- Verify the GraphQL context carries a valid current_user and session (token not expired)
- Check the object id resolves to the intended resource in the same account/shard
- 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
- Remember 'not found' from Canvas mutations usually means permission denied, not missing data
- Verify the token/user session is valid and not expired before calling
- Check enrollments and role for the target course/account
- Avoid masquerading setups that drop current_user from context
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
- 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/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)