instructure/canvas-lms · error
Cannot regenerate criteria with learning outcomes attached
Error message
Cannot regenerate criteria with learning outcomes attached
What it means
RubricLlmService#regenerate_criteria_via_llm refuses to regenerate a single criterion that is tied to a learning outcome (criterion[:learning_outcome_id] present). Outcome criteria are managed by the outcomes system, so LLM rewriting them would desync the rubric from the outcome. The caller must exclude such criteria from regeneration.
Solutions
- Filter out criteria with learning_outcome_id present before offering/sending regeneration requests.
- In the UI, hide or disable the regenerate action for outcome-linked criteria.
- If regeneration of outcome criteria is truly needed, regenerate a non-outcome criterion or detach the outcome first (with user awareness of consequences).
Example fix
// before
service.regenerate_criteria_via_llm(..., { criterion_id: outcome_linked_criterion[:id] })
// after
if outcome_linked_criterion[:learning_outcome_id].present?
return render json: { error: 'cannot regenerate outcome criterion' }, status: :unprocessable_entity
end
service.regenerate_criteria_via_llm(..., { criterion_id: outcome_linked_criterion[:id] }) Defensive patterns
Strategy: validation
Validate before calling
criterion = rubric.criteria.find { |c| c[:id].to_s == criterion_id.to_s }
return :skip if criterion && criterion[:learning_outcome_id].present? Type guard
def outcome_linked?(criterion) criterion.is_a?(Hash) && criterion[:learning_outcome_id].present? end
Try / catch
begin
service.regenerate_criteria_via_llm(...)
rescue RuntimeError => e
raise unless e.message == 'Cannot regenerate criteria with learning outcomes attached'
notify_user('outcome-linked criteria cannot be regenerated')
end Prevention
- Disable regenerate controls for criteria whose learning_outcome_id is set in the UI.
- Filter client-side criteria lists to exclude outcome rows from bulk regenerate payloads.
When it happens
Trigger: Calling regenerate_criteria_via_llm with regenerate_options[:criterion_id] pointing at a criterion whose learning_outcome_id is set — i.e. an outcome-aligned rubric row selected for regeneration in the rubric editor.
Common situations: User clicks a regenerate control on a criterion that Canvas linked to a learning outcome; API client lists all criteria ids including outcome-linked ones; UI failed to disable regenerate for outcome rows.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot find criterion with id #
- LLM generation is only available for rubrics associated…
- The AI response was not in the expected format. Please try…
- User must be associated to rubric before LLM generation
- AI response appears truncated - the response may have…
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/83caf046e3605461.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/rubric_llm_service.rb:119
# criterion:c1:description="Clarity"
# rating:r1:description="Exemplary"
def regenerate_criteria_via_llm(association_object, regenerate_options = {}, generate_options = {})
validate_rubric_and_association_object(association_object)
assignment = association_object
generate_options = resolve_regenerate_options(generate_options, regenerate_options)
incoming_criteria, existing_criteria_json, criteria_as_text, regenerable_criteria, learning_outcome_criteria_map, target_criterion =
normalize_incoming_criteria(regenerate_options)
criterion_id = regenerate_options[:criterion_id]
# Check if trying to regenerate a learning outcome criterion (not allowed)
if criterion_id.present?
if target_criterion.nil?
raise "Cannot find criterion with id #{criterion_id}"
end
if target_criterion[:learning_outcome_id].present?
raise "Cannot regenerate criteria with learning outcomes attached"
end
end
# If all criteria have learning outcomes, there's nothing to regenerate
# Return the original criteria with recalculated points
# Preserve all fields: learning_outcome_id, ignore_for_scoring, mastery_points, generated, etc.
if regenerable_criteria.empty?
total_points = generate_options[:total_points].to_f
points_per_criterion = calculate_points_per_criterion(total_points, incoming_criteria.size)
return incoming_criteria.each_with_index.map do |criterion, index|
criterion.dup.tap do |c|
c[:points] = points_per_criterion[index]
# Normalize ratings from hash to array format for frontend compatibility
c[:ratings] = normalize_ratings_array(c[:ratings]) if c[:ratings].present?
end
end
endView on GitHub (pinned to 1c9f0bb801)