instructure/canvas-lms · error · RuntimeError
`increment_completion!` can only be invoked after a total…
Error message
`increment_completion!` can only be invoked after a total has been set with `calculate_completion!`
What it means
Progress#increment_completion! requires that Progress#calculate_completion! has already run to initialize @total (and @current_value). Calling increment_completion! before a total exists would divide by zero/nil, so the model raises this guard error. It protects the completion-percentage bookkeeping invariant.
Solutions
- Call calculate_completion!(total, current_value) once at the start before any increment_completion! calls
- Check that the same Progress instance is used for both calls (don't reload between them)
- For persisted records, verify completion/total columns are set before incrementing (re-initialize if total is nil)
- Default to calculate_completion! in the initialization path of the background job that tracks progress
Example fix
// before progress.increment_completion! // after progress.calculate_completion!(total_items, 0) progress.increment_completion!
Defensive patterns
Strategy: validation
Validate before calling
raise 'progress total not initialized' if progress.instance_variable_get(:@total).nil? # or for AR-backed records: initialize first progress.calculate_completion!(total, 0) if progress.total.nil?
Type guard
can_increment = ->(p) { p.instance_variable_get(:@total).present? } Try / catch
begin
progress.increment_completion!
rescue RuntimeError => e
Rails.logger.warn("#{e.message}; initializing progress")
progress.calculate_completion!(total, 0)
retry
end Prevention
- Always call calculate_completion! immediately after creating/finding a Progress record
- Keep a single Progress instance for the whole job lifecycle
- Assert @total is set in tests that exercise increment_completion!
- Centralize progress updates in one helper that owns initialization
When it happens
Trigger: Calling progress.increment_completion!(n) on a Progress object/row where calculate_completion!(total, current_value) was never invoked (or was invoked on a different instance than the one reloaded from DB).
Common situations: Job or controller code increments progress on a freshly found Progress record without first calling calculate_completion!; long-running process caches a stale instance after reset; refactoring removed the initial calculate_completion! call.
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
- can't accept
- There is no event # defined for the # state
- A new_id, '# ', referenced an existing # and the # with #…
- A new_integration_id, '#
- A student referenced a non-existent user #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/822cb372ff571446.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/progress.rb:101
end
def set_results(results)
self.results = results
save
end
def update_completion!(value)
update_attribute(:completion, value)
end
def calculate_completion!(current_value, total)
@total = total
@current_value = current_value
update_completion!(100.0 * @current_value / @total)
end
def increment_completion!(increment = 1)
raise "`increment_completion!` can only be invoked after a total has been set with `calculate_completion!`" if @total.nil?
@current_value += increment
new_value = 100.0 * @current_value / @total
# only update the db if we're at a different integral percentage point or it's been > 15s
if new_value.to_i != completion.to_i || (Time.now.utc - updated_at) > 15
update_completion!(new_value)
else
self.completion = new_value
end
end
def pending?
queued? || running? || waiting_for_external_tool?
end
# Tie this Progress model to a delayed job. Rather than `obj.delay.long_method`, use:
# `progress.process_job(obj, :long_method)`. This will transition from queued
# => running when the job starts, from running => completed when the jobView on GitHub (pinned to 1c9f0bb801)