instructure/canvas-lms · error

Course pace is deleted

Error message

Course pace is deleted

What it means

CoursePace#publish refuses to publish a course pace whose workflow_state is 'deleted'. Publishing recomputes assignment due dates for student enrollments, which makes no sense for a deleted pace, so it raises immediately.

Solutions

  1. Check course_pace.deleted? before calling publish and skip deleted paces.
  2. Reload the record before publishing and handle deletion that happened after enqueueing.
  3. Recreate/un-delete the pace if it should be published (restore workflow_state).

Example fix

// before
pace.publish(progress)

// after
pace.reload
pace.publish(progress) unless pace.deleted?
Defensive patterns

Strategy: validation

Validate before calling

raise 'pace deleted' if course_pace.reload.deleted?

Type guard

publishable = ->(p) { p.reload.persisted? && !p.deleted? }

Try / catch

// rescue
begin
  pace.publish(progress)
rescue RuntimeError => e
  raise unless e.message == 'Course pace is deleted'
  Rails.logger.info("skipping deleted pace #{pace.id}")
end

Prevention

When it happens

Trigger: Calling course_pace.publish (directly or from batch/progress jobs) on a pace record with deleted? true; enqueuing publish jobs for paces that were deleted before the job ran.

Common situations: A user deletes a pace while a background publish job is queued; batch automation iterates course_paces without filtering out deleted ones; a race between deletion and publish endpoints.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/18ef58846af8c5bc. Report an issue: GitHub.

Appendix: source

Thrown at app/models/course_pace.rb:157

    progress = Progress.create!(context: self, tag: "course_pace_publish")
    run_publish_progress(progress, run_at:, enrollment_ids:)
    progress
  end

  def run_publish_progress(progress, run_at: Time.zone.now, enrollment_ids: nil)
    progress.process_job(self,
                         :publish,
                         {
                           run_at:,
                           singleton: "course_pace_publish:#{global_id}",
                           on_conflict: :overwrite
                         },
                         enrollment_ids:)
    progress
  end

  def publish(progress = nil, enrollment_ids: nil)
    raise "Course pace is deleted" if deleted?

    enrollments = enrollment_ids ? course.student_enrollments.where(id: enrollment_ids) : student_enrollments
    Time.use_zone(course.time_zone) do
      assignments_to_refresh = Set.new
      Assignment.suspend_due_date_caching do
        Assignment.suspend_grading_period_grade_recalculation do
          progress&.calculate_completion!(0, enrollments.size)
          enrollments.each do |enrollment|
            course_pace_module_items.each(&:restore_attributes)
            # Compressor handles sorting internally
            compressed_module_items = compress_dates(start_date: nil, enrollment:)
            dates =
              CoursePaceDueDatesCalculator.new(self).get_due_dates(compressed_module_items, enrollment)
            course_pace_module_items.each do |course_pace_module_item|
              content_tag = course_pace_module_item.module_item
              assignment = content_tag.assignment
              next unless assignment
              next if assignment.only_visible_to_overrides

View on GitHub (pinned to 1c9f0bb801)