instructure/canvas-lms · error · Checkpoints::CheckpointNotFoundError
Checkpoint '# ' not found
Error message
Checkpoint '#{@checkpoint_label}' not found What it means
DiscussionCheckpointUpdaterService#find_checkpoint looks up a sub-assignment by sub_assignment_tag matching @checkpoint_label (e.g. 'reply_to_topic' or 'reply_to_entry') and raises CheckpointNotFoundError if none exists. The updater can only modify checkpoints that actually exist on the parent assignment.
Solutions
- Verify @checkpoint_label exactly matches an existing sub_assignment_tag: assignment.sub_assignments.pluck(:sub_assignment_tag).
- Create the checkpoint first (creator service) before updating it.
- Trim/canonicalize the tag at the API boundary to avoid whitespace/case mismatches.
Example fix
// before updater.call(assignment: a, checkpoint_label: 'replies', ...) // after updater.call(assignment: a, checkpoint_label: 'reply_to_entry', ...)
Defensive patterns
Strategy: try-catch
Validate before calling
tags = assignment.sub_assignments.pluck(:sub_assignment_tag)
raise ArgumentError, "unknown checkpoint #{label}" unless tags.include?(label) Type guard
def checkpoint_exists?(assignment, label) assignment.sub_assignments.exists?(sub_assignment_tag: label) end
Try / catch
begin
Checkpoints::DiscussionCheckpointUpdaterService.call(assignment:, checkpoint_label: label, ...)
rescue Checkpoints::CheckpointNotFoundError => e
render json: { error: e.message }, status: :not_found
end Prevention
- Fetch valid checkpoint labels from the assignment before building update payloads
- Don't reuse update payloads across different discussions
- Ensure checkpoints exist (creator ran) before issuing updates
When it happens
Trigger: Calling the updater with checkpoint_label that doesn't match any sub_assignment_tag on the assignment — misspelled tag, tag on a different assignment, or the assignment has no sub_assignments at all.
Common situations: API clients using wrong tag strings ('replies' vs 'reply_to_entry'); applying an update payload built for one discussion to another; the checkpoints having been deleted (checkpoints flag disabled) while updates keep arriving; case/whitespace differences in tags.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Checkpoints not found
- discussion_checkpoints feature flag must be enabled
- each date must have a type specified ('everyone' or…
- Course does not exist
- # with sis id # didn't exist for # # .
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/f98d4170fdf1db7a.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/checkpoints/discussion_checkpoint_updater_service.rb:40
validate_flag_enabled
validate_dates
checkpoint = find_checkpoint
compute_due_dates_and_create_submissions(checkpoint)
checkpoint.saved_by = @saved_by
checkpoint.updating_user = @updating_user
checkpoint.save!
checkpoint.saved_by = nil
checkpoint
end
private
def find_checkpoint
@discussion_topic.update!(reply_to_entry_required_count: @replies_required) if update_required_replies?
checkpoint = @assignment.sub_assignments.find_by(sub_assignment_tag: @checkpoint_label)
raise Checkpoints::CheckpointNotFoundError, "Checkpoint '#{@checkpoint_label}' not found" unless checkpoint
# Only assign attributes that have actually changed to avoid triggering
# Master Course validation on unchanged restricted columns
changed_attributes = checkpoint_attributes_for_update(checkpoint)
checkpoint.assign_attributes(changed_attributes) if changed_attributes.any?
update_overrides = override_dates.select { |override| override[:id].present? }
new_overrides = override_dates.select { |override| override[:id].nil? }
existing_overrides = checkpoint.assignment_overrides
override_ids_to_delete = existing_overrides.pluck(:id) - update_overrides.pluck(:id)
# 1. Remove overrides that are no longer present
if override_ids_to_delete.any?
parent_override_ids = checkpoint.assignment_overrides.where(id: override_ids_to_delete).pluck(:parent_override_id)
checkpoint.assignment_overrides.where(id: override_ids_to_delete).destroy_all
AssignmentOverride.where(id: parent_override_ids).destroy_allView on GitHub (pinned to 1c9f0bb801)