{"record":{"id":"1c8d0d8576ffacba","repo":"instructure/canvas-lms","slug":"unable-to-find-learningoutcome-for-id-id","errorCode":null,"errorMessage":"unable to find LearningOutcome for id %{id}","messagePattern":"unable to find LearningOutcome for id %(.+?)","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"warning","filePath":"app/graphql/mutations/update_learning_outcome.rb","lineNumber":50,"sourceCode":"    outcome_input = attrs(input, record.context)\n\n    unless account_level_mastery_scales_enabled?(record.context)\n      update_rubric_criterion(record, outcome_input)\n      outcome_input.delete(:rubric_criterion)\n    end\n\n    record.saving_user = current_user\n    if record.update(outcome_input)\n      { learning_outcome: record }\n    else\n      errors_for(record, { short_description: :title })\n    end\n  end\n\n  private\n\n  def validate!(outcome, outcome_id)\n    raise GraphQL::ExecutionError, I18n.t(\"unable to find LearningOutcome for id %{id}\", id: outcome_id) unless outcome\n\n    raise GraphQL::ExecutionError, I18n.t(\"insufficient permissions\") unless check_permission(outcome)\n  end\n\n  def check_permission(outcome)\n    outcome.grants_right? current_user, :update\n  end\n\n  def update_rubric_criterion(outcome, input)\n    return unless input[:rubric_criterion]\n\n    mastery_points = input[:rubric_criterion][:mastery_points]\n    ratings = input[:rubric_criterion][:ratings]\n    updated_criterion = outcome.rubric_criterion\n    updated_criterion ||= {}\n\n    if mastery_points\n      updated_criterion[:mastery_points] = mastery_points","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/update_learning_outcome.rb#L32-L68","documentation":"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.","triggerScenarios":"Calling updateLearningOutcome with input.id that does not correspond to any LearningOutcome row (deleted, wrong shard, or never existed).","commonSituations":"Stale id cached in a client after outcome deletion; cross-account id confusion; data not migrated between environments; typo in the id.","solutions":["Confirm the LearningOutcome id exists (LearningOutcome.find_by(id:) in console).","Re-fetch the outcome id from the API before mutating; the cached id may be stale.","Check the outcome was not deleted and you are on the right shard/context.","Handle the GraphQL error client-side and surface a 'not found' message to the user."],"exampleFix":"// before\nupdateLearningOutcome(input: {id: \"999\"}) { ... } // outcome deleted\n// after\n# const outcome = await fetchOutcome(id); if (!outcome) showNotFound();\nupdateLearningOutcome(input: {id: \"17\"}) { ... }","handlingStrategy":"validation","validationCode":"const outcome = await gql(FETCH_LEARNING_OUTCOME, { id });\nif (!outcome?.learningOutcome) throw new Error(`LearningOutcome ${id} not found`);","typeGuard":"function outcomeExists(data) { return data?.learningOutcome != null; }","tryCatchPattern":"try {\n  await gql(UPDATE_LEARNING_OUTCOME, { id, ...attrs });\n} catch (e) {\n  if (/unable to find LearningOutcome/.test(e.message)) showNotFound();\n  else throw e;\n}","preventionTips":["Re-fetch outcome ids before mutating; never use long-cached ids","Check the outcome wasn't soft/deleted","Verify environment/shard consistency"],"tags":["graphql","learning-outcome","record-not-found"],"backgroundTag":"record-not-found","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}