{"record":{"id":"45428140d5b0cbde","repo":"instructure/canvas-lms","slug":"outcome-outcome-id-is-not-available-in-context-context-type-454281","errorCode":null,"errorMessage":"Outcome %{outcome_id} is not available in context %{context_type}#%{context_id}","messagePattern":"Outcome %(.+?) is not available in context %(.+?)#%(.+?)","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/set_friendly_description.rb","lineNumber":77,"sourceCode":"      friendly_description.save!\n\n    else\n      friendly_description.destroy if friendly_description.persisted?\n      friendly_description.description = \"\"\n    end\n\n    {\n      outcome_friendly_description: friendly_description\n    }\n  end\n\n  private\n\n  def validate!(context, outcome)\n    verify_authorized_action!(context, :manage_outcomes)\n\n    unless context.available_outcome(outcome.id, allow_global: true)\n      raise GraphQL::ExecutionError, I18n.t(\n        \"Outcome %{outcome_id} is not available in context %{context_type}#%{context_id}\",\n        outcome_id: outcome.id.to_s,\n        context_id: context.id.to_s,\n        context_type: context.class.name\n      )\n    end\n  end\n\n  def get_context(context_type, context_id)\n    unless VALID_CONTEXTS.include?(context_type)\n      raise GraphQL::ExecutionError, I18n.t(\"Invalid context type\")\n    end\n\n    context_type.constantize.find_by(id: context_id).tap do |context|\n      unless context\n        raise GraphQL::ExecutionError, I18n.t(\n          \"No such context for %{context_type}#%{context_id}\",\n          context_type:,","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/set_friendly_description.rb#L59-L95","documentation":"Raised in SetFriendlyDescriptionMutation#validate! after permission checks pass, when context.available_outcome(outcome.id, allow_global: true) returns nil. This means the outcome being given a friendly description is not part of the context's available outcome set (course/account outcome tree, even counting global outcomes), so the mutation refuses to attach a friendly description in that context.","triggerScenarios":"Calling the setFriendlyDescription mutation with an outcomeId that is not linked/available in the given context (context_type + context_id): an outcome from another course/account, a deleted or unlinked outcome, or an outcome id supplied for the wrong context entirely.","commonSituations":"Frontend passing an outcome id from a different course after a copy/import; outcomes removed from an account's outcome tree while a stale UI still references them; global outcomes used with a context whose account chain does not include them; scripts iterating outcome ids from one environment against another.","solutions":["Verify the outcome is actually available in the target context (Course#available_outcome / account outcome tree) and use an outcome that is linked there","Check the outcome_id/context pairing: ensure you are passing the context where the outcome was imported or linked","If the outcome should be available, add it to the context (import/link the outcome into the course or account) and retry","Restore or re-add the outcome if it was deleted or unlinked from the context's outcome tree"],"exampleFix":"// before: blindly calling with any outcome id\nsetFriendlyDescription(input: { contextId, contextType: 'Course', outcomeId, description })\n\n// after: pre-check availability in context\nconst outcome = course.outcomes.find(o => o.id === outcomeId);\nif (!outcome) throw new Error(`outcome ${outcomeId} not available in course ${contextId}`);\nsetFriendlyDescription(input: { contextId, contextType: 'Course', outcomeId, description });","handlingStrategy":"validation","validationCode":"async function assertOutcomeInContext(contextType, contextId, outcomeId) {\n  const endpoint = contextType === 'Account'\n    ? `/api/v1/accounts/${contextId}/outcomes?outcome_ids[]=${outcomeId}`\n    : `/api/v1/courses/${contextId}/outcome_group_links`;\n  const res = await fetch(endpoint);\n  const data = await res.json();\n  const linked = Array.isArray(data)\n    ? data.some(l => l?.outcome?.id === outcomeId)\n    : (data?.outcome_groups || []).some(g => (g.outcomes || []).some(o => o.id === outcomeId));\n  if (!linked) throw new Error(`Outcome ${outcomeId} not available in ${contextType} ${contextId}`);\n}","typeGuard":"const hasLinkedOutcome = (outcomeLinks, outcomeId) =>\n  Array.isArray(outcomeLinks) &&\n  outcomeLinks.some(l => Number(l?.outcome?.id) === Number(outcomeId));","tryCatchPattern":"try {\n  await setFriendlyDescription({ variables: { contextType, contextId, outcomeId, description } });\n} catch (e) {\n  if (e.message.includes('is not available in context')) {\n    showBanner('This outcome is not linked to this course/account.');\n  } else { throw e; }\n}","preventionTips":["Source outcomeIds from the same context's outcome list, never from a different course or environment","Re-fetch outcome links after imports/copies or outcome deletions instead of caching ids","Keep the outcomeId and contextType/contextId pair from the same UI selection event","Prefer global friendly descriptions only for outcomes genuinely available globally"],"tags":["graphql","canvas-lms","outcomes","validation","context"],"backgroundTag":"resource-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"}