instructure/canvas-lms · error · Checkpoints::StudentIdsRequiredError
student_ids is required, but was not provided
Error message
student_ids is required, but was not provided
What it means
Checkpoints::AdhocOverrideUpdaterService updates an existing adhoc override's student list. `call` resolves desired_student_ids from the @override hash, falling back to the IDs currently on override.set; if both sources yield a blank value it raises StudentIdsRequiredError. The update cannot proceed without a target set of students.
Solutions
- Supply a non-empty student_ids array in the @override hash to the service.
- Verify the existing override actually has AssignmentOverrideStudents rows; if the goal is removal, delete the override instead of updating with an empty list.
- Seed valid student IDs (ids present in checkpoint.course.all_students) since invalid ids are silently filtered out and may leave the list blank.
Example fix
// before
AdhocOverrideUpdaterService.call(..., override: {id: ov.id, student_ids: []})
// after
AdhocOverrideUpdaterService.call(..., override: {id: ov.id, student_ids: valid_student_ids}) Defensive patterns
Strategy: validation
Validate before calling
ids = payload[:student_ids].presence || override.assignment_override_students.pluck(:user_id) raise ArgumentError, 'no students to update' if ids.blank?
Type guard
def updatable_student_ids?(override, payload) Array(payload[:student_ids]).any? || override.set.any? end
Try / catch
begin Checkpoints::AdhocOverrideUpdaterService.call(...) rescue Checkpoints::StudentIdsRequiredError # override has no students anywhere; delete it instead Checkpoints::OverrideDeleterService.call(...) end
Prevention
- Distinguish update vs delete intents in the API rather than sending empty student lists
- Check the override has existing AssignmentOverrideStudents before updating without student_ids
- Validate IDs against course.all_students before sending
When it happens
Trigger: Calling AdhocOverrideUpdaterService#call where the @override hash has blank/absent :student_ids AND the existing AssignmentOverride has an empty .set (no override students), so the fallback also yields [].
Common situations: Updating an override that was created empty/corrupted (no override students); sending an update payload with student_ids: [] intending a delete but using the updater instead of a deleter; renamed param keys in API layer.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- each date must have a type specified ('everyone' or…
- invalid date type: #
- set_type is required, but was not provided
- set_type of '# ' not supported. Supported types: #
- Student ids are not in course
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/27a53cc619231aff.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/checkpoints/adhoc_override_updater_service.rb:29
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# 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 Checkpoints::AdhocOverrideUpdaterService < Checkpoints::AdhocOverrideCommonService
def call
override = @checkpoint.assignment_overrides.find_by(id: @override[:id], set_type: AssignmentOverride::SET_TYPE_ADHOC)
raise Checkpoints::OverrideNotFoundError unless override
# Fetch the student_ids from @override or fall back to getting them from override.set
desired_student_ids = @override.fetch(:student_ids, nil)
# If desired_student_ids is not provided, use a map of ids from override.set
desired_student_ids ||= override.set.map(&:id)
raise Checkpoints::StudentIdsRequiredError, "student_ids is required, but was not provided" if desired_student_ids.blank?
valid_student_ids = @checkpoint.course.all_students.where(id: desired_student_ids).pluck(:id).uniq
existing_student_ids = override.assignment_override_students.pluck(:user_id)
student_ids_to_delete = existing_student_ids - valid_student_ids
parent_override = override.parent_override
destroy_students(override:, student_ids: student_ids_to_delete)
destroy_students(override: parent_override, student_ids: student_ids_to_delete)
update_override(override:, student_ids: valid_student_ids)
update_override(override: parent_override, student_ids: valid_student_ids, shell_override: true)
override
end
def destroy_students(override:, student_ids:)
return if student_ids.blank?View on GitHub (pinned to 1c9f0bb801)