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
- 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.
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
- 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.
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
- Outcome with ID already exists in another unrelated course…
- Cyclic reference detected when importing
- Database error ( )
- Error importing outcomes
- File has no data
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")
elseView on GitHub (pinned to 1c9f0bb801)