instructure/canvas-lms · error

unable to handle state change: #

Error message

unable to handle state change: #{asked_state}

What it means

UpdateAssignment mutation's resolve handles 'unpublished', 'published', and 'deleted' asked states; anything else hits this raise. It is the exhaustive-case guard converting workflow state into published flags and side effects.

Solutions

  1. Send only 'published', 'unpublished', or 'deleted' as state
  2. Share an enum constant between client and mutation input to prevent drift
  3. Improve the raise to a GraphQL::ExecutionError naming the valid options

Example fix

# before
raise "unable to handle state change: #{asked_state}"
# after
raise GraphQL::ExecutionError, "unable to handle state change: #{asked_state} (expected published, unpublished, or deleted)"
Defensive patterns

Strategy: validation

Validate before calling

const allowedStates = ['published', 'unpublished', 'deleted'];
if (state && !allowedStates.includes(state)) {
  throw new Error(`state must be one of ${allowedStates.join(', ')}`);
}

Type guard

function isUpdateState(s) {
  return ['published', 'unpublished', 'deleted'].includes(s);
}

Try / catch

try {
  await updateAssignment({ state });
} catch (e) {
  if (/unable to handle state change/.test(e.message)) {
    showError('Supported states: published, unpublished, deleted.');
  }
}

Prevention

When it happens

Trigger: Calling updateAssignment with a state value outside {unpublished, published, deleted}, e.g. 'archived', 'draft', or a casing typo ('Published').

Common situations: Older client code sending states removed from the supported set; typos in GraphQL variables; frontend state machine drift.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at app/graphql/mutations/update_assignment.rb:61

    # input. first, update_api_assignment doesnt expect a :state key. instead,
    # it expects a :published key of boolean type.
    # also, if we are required to transition to restored or destroyed, then we
    # need to handle those as well.
    input_hash = input.to_h
    other_update_on_assignment = false
    if input_hash.key? :state
      asked_state = input_hash.delete :state
      case asked_state
      when "unpublished"
        input_hash[:published] = false
        other_update_on_assignment = :ensure_restored
      when "published"
        input_hash[:published] = true
        other_update_on_assignment = :ensure_restored
      when "deleted"
        other_update_on_assignment = :ensure_destroyed
      else
        raise "unable to handle state change: #{asked_state}"
      end
    end

    # modifies input_hash
    prepare_input_params!(input_hash, update_proxy)

    module_ids = prepare_module_ids!(input_hash)

    # make sure to do other required updates
    send(other_update_on_assignment) if other_update_on_assignment

    # ensure the assignment is part of all required modules
    ensure_modules(module_ids) if module_ids

    # normal update now
    @working_assignment.content_being_saved_by(current_user)
    @working_assignment.updating_user = current_user
    result = update_proxy.update_api_assignment(@working_assignment, ActionController::Parameters.new(input_hash), current_user, @working_assignment.context)

View on GitHub (pinned to 1c9f0bb801)