{"record":{"id":"3c1d653bc364cdc3","repo":"instructure/canvas-lms","slug":"invalid-context-type-set-friendly-description","errorCode":null,"errorMessage":"Invalid context type","messagePattern":"Invalid context type","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/set_friendly_description.rb","lineNumber":88,"sourceCode":"\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:,\n          context_id: context_id.to_s\n        )\n      end\n    end\n  end\n\n  def get_outcome(outcome_id)\n    LearningOutcome.active.find_by(id: outcome_id).tap do |outcome|\n      unless outcome\n        raise GraphQL::ExecutionError, I18n.t(\n          \"No such outcome for id %{id}\", { id: outcome_id }","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/set_friendly_description.rb#L70-L106","documentation":"Raised in SetFriendlyDescriptionMutation#get_context when the supplied context_type string is not in VALID_CONTEXTS (the whitelist of context classes the mutation supports, e.g. Course/Account). The mutation rejects the request before even attempting to load a record, because constantize on arbitrary strings would be unsafe.","triggerScenarios":"Calling setFriendlyDescription with contextType values that are not whitelisted, e.g. lowercase 'course', 'Section', 'User', 'Group', or any other class name outside VALID_CONTEXTS.","commonSituations":"Frontend deriving context type from a polymorphic asset string like 'course_123' and passing the raw lowercase segment; new integration code guessing context types; typos ('Couse', 'Courses'); using valid Canvas contexts (sections, groups) the mutation simply does not support.","solutions":["Pass exactly one of the whitelisted context types (check VALID_CONTEXTS in app/graphql/mutations/set_friendly_description.rb — 'Course' or 'Account') with correct capitalization","Normalize the caller's context type string (e.g. 'course'.capitalize) before sending the mutation","If the object is a Section/Group/User, find the owning Course or Account and use that as the context instead","If a new context type is legitimately needed, add it to VALID_CONTEXTS and redeploy (server-side change)"],"exampleFix":"// before: raw polymorphic string\nconst [type, id] = assetString.split('_');\nsetFriendlyDescription(input: { contextType: type, contextId: id, ... }) // 'course' -> Invalid context type\n\n// after: normalize and whitelist\nconst CONTEXT_TYPES = ['Course', 'Account'];\nconst type = assetString.split('_')[0].capitalize;\nif (!CONTEXT_TYPES.includes(type)) throw new Error(`unsupported context type: ${type}`);\nsetFriendlyDescription(input: { contextType: type, contextId: id, ... });","handlingStrategy":"validation","validationCode":"const VALID_CONTEXTS = ['Course', 'Account'];\nfunction normalizeContextType(assetStringOrType) {\n  const raw = assetStringOrType.includes('_')\n    ? assetStringOrType.split('_')[0]\n    : assetStringOrType;\n  const type = raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase();\n  if (!VALID_CONTEXTS.includes(type)) {\n    throw new Error(`Unsupported context type: ${raw}. Use Course or Account.`);\n  }\n  return type;\n}","typeGuard":"const isValidContextType = (t) =>\n  typeof t === 'string' && ['Course', 'Account'].includes(t);","tryCatchPattern":"try {\n  await setFriendlyDescription({ variables: { contextType, contextId, outcomeId, description } });\n} catch (e) {\n  if (e.message === 'Invalid context type') {\n    console.error(`Bad contextType '${contextType}'; expected Course or Account`);\n  } else { throw e; }\n}","preventionTips":["Normalize asset strings ('course_42') to 'Course' before calling the mutation","Use a shared constant/enum for context types instead of ad-hoc strings","Never pass lowercase, pluralized, or unlisted types like 'Section' or 'User' — find the owning Course/Account","Add a unit test for every contextType value your integration can emit"],"tags":["graphql","canvas-lms","validation","enum","context-type"],"backgroundTag":"invalid-enum-value","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"}