{"record":{"id":"bf1c6bc7341333ba","repo":"instructure/canvas-lms","slug":"expected-an-id-for-expected-type","errorCode":null,"errorMessage":"expected an id for #{expected_type}","messagePattern":"expected an id for #(.+?)","errorType":"exception","errorClass":"InvalidIDError","httpStatus":null,"severity":"error","filePath":"app/graphql/graphql_helpers.rb","lineNumber":56,"sourceCode":"        parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)\n      end\n    rescue InvalidIDError => e\n      GraphQL::ExecutionError.new(e.message)\n    end\n  end\n\n  def self.parse_relay_or_legacy_id(relay_or_legacy_id, expected_type)\n    if relay_or_legacy_id.nil? || relay_or_legacy_id =~ /\\A\\d+\\Z/\n      relay_or_legacy_id\n    else\n      parse_relay_id(relay_or_legacy_id, expected_type)\n    end\n  end\n\n  def self.parse_relay_id(relay_id, expected_type)\n    type, id = GraphQL::Schema::UniqueWithinType.decode(relay_id)\n    if type != expected_type || id.nil?\n      raise InvalidIDError, \"expected an id for #{expected_type}\"\n    else\n      id\n    end\n  end\n\n  # TODO: move this into LockType after we switch to the class-based api\n  def self.make_lock_resolver(attr)\n    lambda do |lock, _, _|\n      if lock == false\n        nil\n      else\n        lock[attr]\n      end\n    end\n  end\n\n  class InvalidIDError < StandardError; end\nend","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/graphql_helpers.rb#L38-L74","documentation":"GraphQLHelpers.parse_relay_id decodes a Relay global id via UniqueWithinType.decode and validates that the embedded type equals expected_type and the id is non-nil. Mismatches raise InvalidIDError 'expected an id for <expected_type>'.","triggerScenarios":"Passing a Relay id of the wrong type to a field expecting a specific type (e.g. a Course gid where a User id is expected), or passing a malformed/garbage base64 string that decodes to nil.","commonSituations":"Copying ids between different queries; using raw database ids instead of Relay gids; clients constructing gids manually with the wrong type name; cross-shard id formatting mistakes.","solutions":["Encode the correct global id with GraphQL::Schema::UniqueWithinType.encode(expected_type, id).","Check that the id you pass matches the field's expected argument type in the schema.","If you have a raw db id, wrap it: GraphQL::Schema::UniqueWithinType.encode('User', db_id)."],"exampleFix":"// before\nuser(id: \"Q291cnNlLTE=\") // decodes to Course-1\n// after\nconst gid = Buffer.from('User-7').toString('base64') // 'VXNlci03'\nuser(id: \"VXNlci03\")","handlingStrategy":"validation","validationCode":"function decodeGid(gid){ const [t, id] = GraphQL::Schema::UniqueWithinType.decode(gid); return {t, id} }\nif (decodeGid(gid).t !== 'Course') throw new Error('wrong gid type for this field')","typeGuard":"function isCourseGid(gid){ try { return UniqueWithinType.decode(gid)[0] === 'Course' } catch { return false } }","tryCatchPattern":"begin\n  Helpers.parse_relay_id(gid, 'Course')\nrescue InvalidIDError\n  # re-encode from raw db id or surface a client error\nend","preventionTips":["Always take gids from prior GraphQL responses","Never hand-decode/encode Relay ids ad hoc","Add client-side helpers typed per entity"],"tags":["graphql","relay","id"],"backgroundTag":"invalid-identifier-format","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"}