{"record":{"id":"5addb42720af706e","repo":"instructure/canvas-lms","slug":"checkpoints-not-found","errorCode":null,"errorMessage":"Checkpoints not found","messagePattern":"Checkpoints not found","errorType":"exception","errorClass":"Checkpoints::NoCheckpointsFoundError","httpStatus":null,"severity":"error","filePath":"app/services/checkpoints/discussion_checkpoint_deleter_service.rb","lineNumber":48,"sourceCode":"    checkpoints.each do |checkpoint|\n      checkpoint.active_assignment_overrides.destroy_all\n    end\n\n    @assignment.active_assignment_overrides.destroy_all\n\n    checkpoints.destroy_all\n\n    update_assignment_and_discussion\n\n    true\n  end\n\n  private\n\n  def find_checkpoints\n    checkpoints = @assignment.sub_assignments\n\n    raise Checkpoints::NoCheckpointsFoundError, \"Checkpoints not found\" unless checkpoints.any?\n\n    checkpoints\n  end\n\n  def update_assignment_and_discussion\n    @assignment.update!(has_sub_assignments: false)\n    @discussion_topic.update!(reply_to_entry_required_count: 0)\n  end\nend\n","sourceCodeStart":30,"sourceCodeEnd":58,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/checkpoints/discussion_checkpoint_deleter_service.rb#L30-L58","documentation":"DiscussionCheckpointDeleterService#find_checkpoints loads @assignment.sub_assignments and raises NoCheckpointsFoundError if there are none. The deleter is meant to tear down checkpoint sub-assignments for a discussion with has_sub_assignments; if the assignment has no sub-assignments there is nothing to delete, indicating the assignment isn't checkpointed (anymore).","triggerScenarios":"Calling the deleter on an assignment whose has_sub_assignments is false or whose sub_assignments were already removed (double delete), e.g. duplicate delete requests or deleting checkpoints on a regular (non-checkpoint) discussion.","commonSituations":"Idempotency issues where the UI fires delete twice and the second call fails; calling the deleter for a plain discussion topic; data migrations/fixups that cleared sub_assignments without updating has_sub_assignments.","solutions":["Check assignment.has_sub_assignments? (and sub_assignments.any?) before calling the deleter and treat no-op as success.","Make the caller idempotent: rescue NoCheckpointsFoundError and skip.","If data is inconsistent (has_sub_assignments true but no sub_assignments), repair by setting has_sub_assignments: false instead of deleting."],"exampleFix":"// before\nCheckpoints::DiscussionCheckpointDeleterService.call(assignment: a)\n// after\nif a.has_sub_assignments? && a.sub_assignments.any?\n  Checkpoints::DiscussionCheckpointDeleterService.call(assignment: a)\nend","handlingStrategy":"try-catch","validationCode":"return :noop unless assignment.has_sub_assignments? && assignment.sub_assignments.any?","typeGuard":null,"tryCatchPattern":"begin\n  Checkpoints::DiscussionCheckpointDeleterService.call(assignment:)\nrescue Checkpoints::NoCheckpointsFoundError\n  # already deleted or never checkpointed; treat as idempotent success\nend","preventionTips":["Make delete operations idempotent by checking sub_assignments.any? first","Keep has_sub_assignments in sync when sub_assignments change","Guard UI delete buttons on the assignment actually having checkpoints"],"tags":["ruby","canvas","resource-not-found","checkpoints"],"backgroundTag":"empty-result-set","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"}