{"record":{"id":"187c363c6c9332bd","repo":"instructure/canvas-lms","slug":"cannot-modify-outcome-from-another-context-changes-outcome","errorCode":null,"errorMessage":"Cannot modify outcome from another context: %{changes}; outcome must be modified in %{context}","messagePattern":"Cannot modify outcome from another context: %(.+?); outcome must be modified in %(.+?)","errorType":"exception","errorClass":"InvalidDataError","httpStatus":null,"severity":"error","filePath":"lib/outcomes/import.rb","lineNumber":191,"sourceCode":"      model.vendor_guid = outcome[:vendor_guid]\n      model.title = outcome[:title]\n      model.description = infer_nil_value(model, :description, outcome)\n      model.display_name = infer_nil_value(model, :display_name, outcome)\n      model.calculation_method = outcome[:calculation_method].presence || model.default_calculation_method\n      model.calculation_int = outcome[:calculation_int].presence || model.default_calculation_int\n      # let removing the outcome_links content tags delete the underlying outcome\n      model.workflow_state = \"active\" unless outcome[:workflow_state] == \"deleted\"\n\n      prior_rubric = model.rubric_criterion || {}\n      changed = ->(k) { outcome[k].present? && outcome[k] != prior_rubric[k] }\n      rubric_change = changed.call(:ratings) || changed.call(:mastery_points)\n      model.rubric_criterion = create_rubric(outcome[:ratings], outcome[:mastery_points]) if rubric_change\n\n      if model.context == context\n        model.outcome_import_id = outcome_import_id\n        model.save!\n      elsif non_vendor_guid_changes?(model)\n        raise InvalidDataError, I18n.t(\n          \"Cannot modify outcome from another context: %{changes}; outcome must be modified in %{context}\",\n          changes: model.changes.keys.inspect,\n          context: if model.context.present?\n                     I18n.t('\"%{name}\"', name: model.context.name)\n                   else\n                     I18n.t(\"the global context\")\n                   end\n        )\n      end\n\n      parents = [] if outcome[:workflow_state] == \"deleted\"\n      update_outcome_parents(model, parents, allow_indirect:)\n\n      if outcome[:friendly_description].present?\n        fd = OutcomeFriendlyDescription.find_or_create_by(context: model.context, learning_outcome: model)\n        fd.update(description: outcome[:friendly_description])\n        fd.update(workflow_state: \"active\")\n      else","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/outcomes/import.rb#L173-L209","documentation":"Raised by import_outcome when the import targets a context different from the outcome's owning context AND the pending changes include non-vendor-guid fields. Only vendor_guid re-keying is allowed from outside the owner context; substantive edits (title, description, ratings, mastery points) must happen in the context that owns the outcome, or via a child context relationship.","triggerScenarios":"Importing outcome changes (e.g. new ratings) from an account/course that does not own the outcome and is not related to it, with non_vendor_guid_changes?(model) true — the elsif branch at lib/outcomes/import.rb:191.","commonSituations":"District-level account trying to edit site outcomes it doesn't own; importing an edited export of someone else's outcomes; a migrated course still referencing the original account's outcomes with modified fields.","solutions":["Import the modified outcome from its owning context (or an ancestor of it) instead.","Revert the non-guid changes so only the vendor_guid differs, if cross-context re-keying was intended.","Copy the outcome into your own context (new vendor_guid) and edit the copy.","Ask the owning account's admins to make the change and re-export."],"exampleFix":"// before: import JSON changes title of outcome owned by Account A, imported from Account B\n// after: import from Account A context, or only re-key:\n{\n  \"vendor_guid\": \"A-old-guid -> new-guid\",\n  /* no title/rating changes */\n}","handlingStrategy":"validation","validationCode":"// Ruby: only send substantive changes when importing from the owner context\ndef owns?(context, model)\n  model.context_id == context.id && model.context_type == context.class.name\nend\n# diff payload against current outcome and drop non-guid keys unless owns?","typeGuard":null,"tryCatchPattern":"begin\n  importer.import_object(...)\nrescue Outcomes::Import::InvalidDataError => e\n  if e.message.start_with?('Cannot modify outcome from another context')\n    Rails.logger.warn(e.message)\n  end\nend","preventionTips":["Run outcome edits from the account/course that owns the outcome.","Compare the import payload to the existing outcome and strip unchanged/non-permitted fields.","Use copies with new vendor_guids for local customization instead of editing shared outcomes."],"tags":["outcomes","import","permissions","context-hierarchy"],"backgroundTag":"permission-denied","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"}