instructure/canvas-lms · warning · GraphQL::ExecutionError

unable to find LearningOutcome for id

Error message

unable to find LearningOutcome for id %{id}

What it means

UpdateLearningOutcome.validate! raises this when no LearningOutcome exists for the supplied id. The mutation resolves the outcome (find_by) and, since it is nil, raises a GraphQL::ExecutionError via validate! during resolve.

Solutions

  1. Confirm the LearningOutcome id exists (LearningOutcome.find_by(id:) in console).
  2. Re-fetch the outcome id from the API before mutating; the cached id may be stale.
  3. Check the outcome was not deleted and you are on the right shard/context.
  4. Handle the GraphQL error client-side and surface a 'not found' message to the user.

Example fix

// before
updateLearningOutcome(input: {id: "999"}) { ... } // outcome deleted
// after
# const outcome = await fetchOutcome(id); if (!outcome) showNotFound();
updateLearningOutcome(input: {id: "17"}) { ... }
Defensive patterns

Strategy: validation

Validate before calling

const outcome = await gql(FETCH_LEARNING_OUTCOME, { id });
if (!outcome?.learningOutcome) throw new Error(`LearningOutcome ${id} not found`);

Type guard

function outcomeExists(data) { return data?.learningOutcome != null; }

Try / catch

try {
  await gql(UPDATE_LEARNING_OUTCOME, { id, ...attrs });
} catch (e) {
  if (/unable to find LearningOutcome/.test(e.message)) showNotFound();
  else throw e;
}

Prevention

When it happens

Trigger: Calling updateLearningOutcome with input.id that does not correspond to any LearningOutcome row (deleted, wrong shard, or never existed).

Common situations: Stale id cached in a client after outcome deletion; cross-account id confusion; data not migrated between environments; typo in the id.

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


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

Appendix: source

Thrown at app/graphql/mutations/update_learning_outcome.rb:50

    outcome_input = attrs(input, record.context)

    unless account_level_mastery_scales_enabled?(record.context)
      update_rubric_criterion(record, outcome_input)
      outcome_input.delete(:rubric_criterion)
    end

    record.saving_user = current_user
    if record.update(outcome_input)
      { learning_outcome: record }
    else
      errors_for(record, { short_description: :title })
    end
  end

  private

  def validate!(outcome, outcome_id)
    raise GraphQL::ExecutionError, I18n.t("unable to find LearningOutcome for id %{id}", id: outcome_id) unless outcome

    raise GraphQL::ExecutionError, I18n.t("insufficient permissions") unless check_permission(outcome)
  end

  def check_permission(outcome)
    outcome.grants_right? current_user, :update
  end

  def update_rubric_criterion(outcome, input)
    return unless input[:rubric_criterion]

    mastery_points = input[:rubric_criterion][:mastery_points]
    ratings = input[:rubric_criterion][:ratings]
    updated_criterion = outcome.rubric_criterion
    updated_criterion ||= {}

    if mastery_points
      updated_criterion[:mastery_points] = mastery_points

View on GitHub (pinned to 1c9f0bb801)