instructure/canvas-lms · error · GraphQL::ExecutionError

assignment not found: #

Error message

assignment not found: #{assignment_id}

What it means

UpdateAssignment#resolve parses the input id and calls Assignment.find(assignment_id). A ActiveRecord::RecordNotFound is caught and converted to GraphQL::ExecutionError 'assignment not found: <id>'. The mutation aborts before any permission check or update is performed.

Solutions

  1. Re-fetch the assignment id from the course's assignments connection and retry
  2. Confirm the assignment exists (Assignment.exists?) and is not deleted
  3. Check you are hitting the correct Canvas shard/account for that id
  4. If the assignment was deleted, restore it or drop the mutation

Example fix

// before
mutation { updateAssignment(input: {id: "999", name: "New"}) { ... } }
// after
// resolve the id against the course first
query { course(id: 1) { assignmentsConnection { nodes { id name } } } }
mutation { updateAssignment(input: {id: "<existing-id>", name: "New"}) { ... } }
Defensive patterns

Strategy: try-catch

Validate before calling

const assignments = await canvasQuery(`query { course(id: $cid) { assignmentsConnection { nodes { id } } } }`, {cid});
if (!assignments?.assignmentsConnection?.nodes.some(a => a.id === inputId)) throw new Error(`Assignment ${inputId} not found in course; aborting update`);

Type guard

function isKnownAssignment(node, id) { return node != null && node.id === id; }

Try / catch

try {
  await updateAssignment({ id });
} catch (e) {
  if (/assignment not found/.test(e.message)) {
    // refetch assignment list, surface stale-id error to user
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the updateAssignment mutation with an id that is not a valid Assignment (deleted assignment, wrong shard/context id, or an id that GraphQLHelpers.parse_relay_or_legacy_id resolved to a non-existent record).

Common situations: Client caches a stale assignment id after the assignment was deleted; tests/scripts hard-code ids from another environment; id confusion between Assignment and its discussion_topic/checkpoint sub-assignments.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

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

#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class Mutations::UpdateAssignment < Mutations::AssignmentBase::Mutation
  graphql_name "UpdateAssignment"

  argument :id, ID, required: true
  argument :name, String, required: false
  # most arguments inherited from AssignmentBase

  def resolve(input:)
    assignment_id = GraphQLHelpers.parse_relay_or_legacy_id(input[:id], "Assignment")

    begin
      @working_assignment = Assignment.find(assignment_id)
    rescue ActiveRecord::RecordNotFound
      raise GraphQL::ExecutionError, "assignment not found: #{assignment_id}"
    end

    # check permissions asap
    raise GraphQL::ExecutionError, "insufficient permission" unless @working_assignment.grants_right? current_user, :update

    update_proxy = ApiProxy.new(context[:request], @working_assignment, context[:session], current_user, in_app: context[:in_app])

    # to use the update_api_assignment method, we have to modify some of the
    # 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"

View on GitHub (pinned to 1c9f0bb801)