instructure/canvas-lms · error · ActiveRecord::RecordNotFound
invalid assignment id #
Error message
invalid assignment id #{id} What it means
Assignment::BulkUpdate#run processes a batch of date updates keyed by assignment id. When the id in the payload does not correspond to any assignment in the loaded assignments map (scope-limited to the course/current user's permissions), it raises ActiveRecord::RecordNotFound 'invalid assignment id <id>'. This prevents silently skipping or worse updating an assignment outside the intended scope.
Solutions
- Verify every id in the payload exists and is visible in the target course before submitting
- Refresh the assignments list before bulk edit to drop deleted rows
- Filter payload ids against Assignment.active.where(id: ids, course: course) client- or controller-side
- Rescue ActiveRecord::RecordNotFound and report which id was invalid instead of failing the whole batch blindly
Example fix
// before
Assignment::BulkUpdate.new(course, user, { "17" => dates }).run # 17 deleted
// after
valid_ids = course.assignments.active.where(id: [17]).pluck(:id)
payload.slice(*valid_ids.map(&:to_s)) then run Defensive patterns
Strategy: try-catch
Validate before calling
const ids = payload.map(([id]) => id);
const valid = await canvas.get(`/courses/${courseId}/assignments`, { ids });
if (valid.length !== ids.length) throw new Error('Some assignments no longer exist'); Type guard
const assignmentExists = (id, assignments) => assignments.some(a => String(a.id) === String(id))
Try / catch
begin
Assignment::BulkUpdate.new(course, user, payload).run
rescue ActiveRecord::RecordNotFound => e
render json: { error: e.message }, status: :not_found
end Prevention
- Refresh the assignments list before opening the bulk edit UI
- Scope payload ids to course.assignments.active ids server-side
- Handle per-row failures instead of failing the whole batch
When it happens
Trigger: POSTing to the assignment bulk-update endpoint with an assignment id from another course, a deleted assignment, or one the user cannot read; stale client state after an assignment was deleted; id passed as a non-numeric string so id.to_i mismatches.
Common situations: Frontend batch-edit table holding stale rows after concurrent deletion; users tampering with ids; cross-shard/cross-course id reuse in scripts hitting the API.
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
- invalid assignment override id #
- # not found
- not found
- Resource not found for type: #
- A course did not pass validation
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/a4af3e91aa1418fc.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/assignment/bulk_update.rb:45
delegate :grading_periods?, to: :@context
def run(progress, assignment_data)
# assignment_data looks like [:id, :all_dates => [:id, :base, :due_at, :unlock_at, :lock_at]]
assignment_data_hash = assignment_data.index_by { |a| a["id"] }
assignments = @context.active_assignments.where(id: assignment_data_hash.keys).preload(:assignment_overrides).index_by(&:id)
assignments_to_save = Set.new
# 1. update AR models (in memory!)
assignment_data_hash.each do |id, data|
dates = data["all_dates"]
next unless dates.present?
base, overrides = dates.partition { |date| date["base"] }
# 1a. update the assignment
assignment = assignments[id.to_i]
raise ActiveRecord::RecordNotFound, "invalid assignment id #{id}" unless assignment
if base.any?
assignment.content_being_saved_by(@current_user)
assignment.updating_user = @current_user
assignment.assign_attributes(base.first.slice(*%w[due_at unlock_at lock_at]))
assignments_to_save << assignment if assignment.changed?
end
# 1b. update associated overrides
overrides.each do |override_data|
override = assignment.assignment_overrides.detect { |o| o.id == override_data["id"].to_i }
raise ActiveRecord::RecordNotFound, "invalid assignment override id #{override_data["id"]} for assignment #{assignment.id}" unless override
%w[due_at unlock_at lock_at].each do |date|
if override_data.key?(date)
override.send(:"#{date}=", override_data[date])
override.send(:"#{date}_overridden=", true)
elseView on GitHub (pinned to 1c9f0bb801)