{"record":{"id":"27a53cc619231aff","repo":"instructure/canvas-lms","slug":"student-ids-is-required-but-was-not-provided-adhoc-override","errorCode":null,"errorMessage":"student_ids is required, but was not provided","messagePattern":"student_ids is required, but was not provided","errorType":"validation","errorClass":"Checkpoints::StudentIdsRequiredError","httpStatus":null,"severity":"error","filePath":"app/services/checkpoints/adhoc_override_updater_service.rb","lineNumber":29,"sourceCode":"#\n# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n\nclass Checkpoints::AdhocOverrideUpdaterService < Checkpoints::AdhocOverrideCommonService\n  def call\n    override = @checkpoint.assignment_overrides.find_by(id: @override[:id], set_type: AssignmentOverride::SET_TYPE_ADHOC)\n    raise Checkpoints::OverrideNotFoundError unless override\n\n    # Fetch the student_ids from @override or fall back to getting them from override.set\n    desired_student_ids = @override.fetch(:student_ids, nil)\n    # If desired_student_ids is not provided, use a map of ids from override.set\n    desired_student_ids ||= override.set.map(&:id)\n    raise Checkpoints::StudentIdsRequiredError, \"student_ids is required, but was not provided\" if desired_student_ids.blank?\n\n    valid_student_ids = @checkpoint.course.all_students.where(id: desired_student_ids).pluck(:id).uniq\n    existing_student_ids = override.assignment_override_students.pluck(:user_id)\n    student_ids_to_delete = existing_student_ids - valid_student_ids\n\n    parent_override = override.parent_override\n\n    destroy_students(override:, student_ids: student_ids_to_delete)\n    destroy_students(override: parent_override, student_ids: student_ids_to_delete)\n\n    update_override(override:, student_ids: valid_student_ids)\n    update_override(override: parent_override, student_ids: valid_student_ids, shell_override: true)\n\n    override\n  end\n\n  def destroy_students(override:, student_ids:)\n    return if student_ids.blank?","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/checkpoints/adhoc_override_updater_service.rb#L11-L47","documentation":"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.","triggerScenarios":"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 [].","commonSituations":"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.","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."],"exampleFix":"// before\nAdhocOverrideUpdaterService.call(..., override: {id: ov.id, student_ids: []})\n// after\nAdhocOverrideUpdaterService.call(..., override: {id: ov.id, student_ids: valid_student_ids})","handlingStrategy":"validation","validationCode":"ids = payload[:student_ids].presence || override.assignment_override_students.pluck(:user_id)\nraise ArgumentError, 'no students to update' if ids.blank?","typeGuard":"def updatable_student_ids?(override, payload)\n  Array(payload[:student_ids]).any? || override.set.any?\nend","tryCatchPattern":"begin\n  Checkpoints::AdhocOverrideUpdaterService.call(...)\nrescue Checkpoints::StudentIdsRequiredError\n  # override has no students anywhere; delete it instead\n  Checkpoints::OverrideDeleterService.call(...)\nend","preventionTips":["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"],"tags":["ruby","canvas","validation","service-object"],"backgroundTag":"empty-required-field","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}