instructure/canvas-lms · error · BadPermissionSettingError
Cannot change locked status on granular permission
Error message
Cannot change locked status on granular permission
What it means
Canvas permissions can be granular (e.g. a group like :manage_courses splits into :manage_courses_add/:manage_courses_edit). For such granular permissions, locking the whole group's locked state is disallowed, so set_permissions_for raises BadPermissionSettingError when the :locked key is sent for a group whose members have disable_locking.
Solutions
- Send the locked flag on each individual granular permission instead of the group name (e.g. manage_courses_add, manage_courses_edit).
- Remove the :locked key from permission_updates for granular groups and only set it for lockable permissions.
- Check Permission.all/permission metadata for disable_locking before sending the locked key.
Example fix
// before
permissions: {manage_courses: {locked: true, explicit: true}}
// after
permissions: {manage_courses_add: {locked: true, explicit: true}, manage_courses_edit: {locked: true, explicit: true}} Defensive patterns
Strategy: validation
Validate before calling
const granular = ['manage_courses','manage_students'] // check permission metadata
if (updates[perm]?.locked !== undefined && granular.includes(perm)) throw new Error(`set locked on granular sub-permissions of ${perm} individually`) Try / catch
try { await api.updateRole(roleId, body) } catch (e) { if (e.message.includes('Cannot change locked status')) { /* fall back to per-sub-permission updates */ } else throw e } Prevention
- Consult Canvas permission metadata for disable_locking before sending :locked
- Apply locked flags per granular sub-permission
- Mirror what the Canvas permissions UI sends
When it happens
Trigger: Calling role update or add_role with permissions like {manage_courses: {locked: true}} where manage_courses is a permission group flagged with disable_locking on its member permissions.
Common situations: UI or SIS automation sending a single 'locked' flag for a grouped permission; scripts copied from non-granular permission updates; API clients unaware of the granular-permission refactor.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Permission must be enabled for someone
- Cannot import to other courses
- Failed updating an event in the series, update not saved
- GUID is invalid: #
- ineligible participant
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/17b651e1a1b53457.
Report an issue: GitHub.
Appendix: source
Thrown at app/controllers/role_overrides_controller.rb:667
grouped_permissions = Hash.new { |h, k| h[k] = [] }.with_indifferent_access
manageable_permissions.each do |permission_name, permission|
grouped_permissions[permission_name] << { name: permission_name, disable_locking: permission.key?(:group) }
if permission.key?(:group)
current_override = context.role_overrides.where(permission: permission_name, role_id: role.id).first
grouped_permissions[permission[:group]] << { name: permission_name, disable_locking: false, currently: current_override&.enabled }
end
end
RoleOverride.transaction do
permissions.each do |permission_or_group_name, permission_updates|
next if value_to_boolean(permission_updates[:readonly])
target_permissions = grouped_permissions[permission_or_group_name]
next if target_permissions.empty?
if permission_updates.key?(:locked)
if target_permissions.any? { |permission| permission[:disable_locking] }
raise BadPermissionSettingError, t("Cannot change locked status on granular permission")
else
locked = value_to_boolean(permission_updates[:locked])
end
end
if permission_updates.key?(:enabled) && value_to_boolean(permission_updates[:explicit])
override = value_to_boolean(permission_updates[:enabled])
end
if permission_updates.key? :applies_to_self
applies_to_self = value_to_boolean(permission_updates[:applies_to_self])
end
if permission_updates.key? :applies_to_descendants
applies_to_descendants = value_to_boolean(permission_updates[:applies_to_descendants])
end
if applies_to_descendants == false && applies_to_self == falseView on GitHub (pinned to 1c9f0bb801)