instructure/canvas-lms · error
user/group count mismatch
Error message
user/group count mismatch
What it means
GroupCategory#determine_group_distribution assigns unassigned members across the groups it created, then asserts an internal invariant: the per-group counts must total exactly the number of groups and each section's distribution must sum to that section's user count. If the arithmetic doesn't line up, it raises this RuntimeError to abort before groups are actually populated, since a bad distribution would silently lose or duplicate memberships.
Solutions
- Verify the group category's groups.count matches the number of distribution slots the algorithm computed for the current section user counts
- Recompute in a transaction: build groups and distribute in one pass so user_counts and @groups come from the same data snapshot
- Check for customizations/overrides to create_groups or distribute_students that change @groups after distribution is computed
- Reproduce locally with the same section/user data and log user_counts, @group_distributions, and @groups.count to find which side of the invariant is off
Example fix
// before category.determine_group_distribution // after # ensure groups exist for every section before distribution category.groups.reload raise 'run create_groups first' if category.groups.empty? dist = category.determine_group_distribution
Defensive patterns
Strategy: validation
Validate before calling
users = category.unassigned_users_by_section groups_count = category.groups.count expected = users.values.sum raise 'group count mismatch before distribution' unless groups_count == expected_total_slots(users) # only call determine_group_distribution when counts line up
Try / catch
begin
category.determine_group_distribution
rescue RuntimeError => e
raise e unless e.message == 'user/group count mismatch'
Rails.logger.error('group distribution invariant failed, rebuilding groups')
category.reset_and_rebuild_groups!
end Prevention
- Build groups and compute distributions in one transaction against the same user snapshot
- Reload the association before distribution so group counts are current
- Add tests covering multi-section courses with remainder users
When it happens
Trigger: Calling group_category.determine_group_distribution (directly or via assign_unassigned_members) when @groups was built from a different set of sections/users than user_counts, or when group creation partially failed so @groups.count doesn't match the computed distribution slots.
Common situations: Course sections changed between user-count snapshot and group creation; a plugin or customization overriding group creation; self-signup groups where some groups were deleted mid-distribution; large imports creating users while distribution runs.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cannot validate existence for resource type: #
- Checkpoint '# ' not found
- Checkpoints not found
- Course does not exist
- discussion_checkpoints feature flag must be enabled
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/0b41f128ac430d37.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/group_category.rb:724
leftover_sec_id = group_counts.max_by { |k, count| [-1 * (extra_groups[k] || 0), (user_counts[k].to_f / (count + 1)), k] }.first
group_counts[leftover_sec_id] += 1
extra_groups[leftover_sec_id] ||= 0
extra_groups[leftover_sec_id] += 1
num_groups_assigned += 1
end
end
@group_distributions = {}
group_counts.each do |section_id, num_groups|
# turn them into an array of group sizes, e.g. 7 users into 3 groups becomes [3, 2, 2]
dist = [user_counts[section_id] / num_groups] * num_groups # base
(user_counts[section_id] % num_groups).times do |idx| # distribute remainder around
dist[idx % num_groups] += 1
end
@group_distributions[section_id] = dist
end
if @group_distributions.values.sum(&:count) != @groups.count || @group_distributions.any? { |k, v| v.sum != user_counts[k] }
raise "user/group count mismatch" # we should make sure this works before going any further
end
@group_distributions
end
def assign_students_to_groups
@group_distributions.each do |section_id, group_sizes|
@users_by_section_id[section_id].shuffle!
group_sizes.each do |group_size|
group = @groups.pop
group.bulk_add_users_to_group(@users_by_section_id[section_id].pop(group_size))
end
end
end
end
def clear_permissions_cache_for_selfsignup
return unless %i[self_signup self_signup_end_at].any? { |k| saved_changes.key?(k) } # Skip if neither setting was changedView on GitHub (pinned to 1c9f0bb801)