instructure/canvas-lms · error · InvalidDataError

Cannot modify outcome from another context

Error message

Cannot modify outcome from another context: %{changes}; outcome must be modified in %{context}

What it means

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.

Solutions

  1. Import the modified outcome from its owning context (or an ancestor of it) instead.
  2. Revert the non-guid changes so only the vendor_guid differs, if cross-context re-keying was intended.
  3. Copy the outcome into your own context (new vendor_guid) and edit the copy.
  4. Ask the owning account's admins to make the change and re-export.

Example fix

// before: import JSON changes title of outcome owned by Account A, imported from Account B
// after: import from Account A context, or only re-key:
{
  "vendor_guid": "A-old-guid -> new-guid",
  /* no title/rating changes */
}
Defensive patterns

Strategy: validation

Validate before calling

// Ruby: only send substantive changes when importing from the owner context
def owns?(context, model)
  model.context_id == context.id && model.context_type == context.class.name
end
# diff payload against current outcome and drop non-guid keys unless owns?

Try / catch

begin
  importer.import_object(...)
rescue Outcomes::Import::InvalidDataError => e
  if e.message.start_with?('Cannot modify outcome from another context')
    Rails.logger.warn(e.message)
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at lib/outcomes/import.rb:191

      model.vendor_guid = outcome[:vendor_guid]
      model.title = outcome[:title]
      model.description = infer_nil_value(model, :description, outcome)
      model.display_name = infer_nil_value(model, :display_name, outcome)
      model.calculation_method = outcome[:calculation_method].presence || model.default_calculation_method
      model.calculation_int = outcome[:calculation_int].presence || model.default_calculation_int
      # let removing the outcome_links content tags delete the underlying outcome
      model.workflow_state = "active" unless outcome[:workflow_state] == "deleted"

      prior_rubric = model.rubric_criterion || {}
      changed = ->(k) { outcome[k].present? && outcome[k] != prior_rubric[k] }
      rubric_change = changed.call(:ratings) || changed.call(:mastery_points)
      model.rubric_criterion = create_rubric(outcome[:ratings], outcome[:mastery_points]) if rubric_change

      if model.context == context
        model.outcome_import_id = outcome_import_id
        model.save!
      elsif non_vendor_guid_changes?(model)
        raise InvalidDataError, I18n.t(
          "Cannot modify outcome from another context: %{changes}; outcome must be modified in %{context}",
          changes: model.changes.keys.inspect,
          context: if model.context.present?
                     I18n.t('"%{name}"', name: model.context.name)
                   else
                     I18n.t("the global context")
                   end
        )
      end

      parents = [] if outcome[:workflow_state] == "deleted"
      update_outcome_parents(model, parents, allow_indirect:)

      if outcome[:friendly_description].present?
        fd = OutcomeFriendlyDescription.find_or_create_by(context: model.context, learning_outcome: model)
        fd.update(description: outcome[:friendly_description])
        fd.update(workflow_state: "active")
      else

View on GitHub (pinned to 1c9f0bb801)