{"record":{"id":"475c0a19920b219a","repo":"instructure/canvas-lms","slug":"not-found-create-module","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/create_module.rb","lineNumber":44,"sourceCode":"\n  field :module, Types::ModuleType, null: true, resolver_method: :will_not_be_called\n  def will_not_be_called\n    # This is a silly workaround for https://github.com/rmosolgo/graphql-ruby/issues/2723\n  end\n\n  def resolve(input:)\n    course_id = GraphQLHelpers.parse_relay_or_legacy_id(input[:course_id], \"Course\")\n    course = Course.find(course_id)\n    verify_authorized_action!(course.context_modules.temp_record, :create)\n    mod = course.context_modules.build(name: input[:name])\n    mod.require_presence_of_name = true\n    if mod.save\n      { module: mod }\n    else\n      errors_for(mod)\n    end\n  rescue ActiveRecord::RecordNotFound\n    raise GraphQL::ExecutionError, \"not found\"\n  end\nend\n","sourceCodeStart":26,"sourceCodeEnd":47,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/create_module.rb#L26-L47","documentation":"CreateModule#resolve rescues ActiveRecord::RecordNotFound and converts it to GraphQL::ExecutionError 'not found'. The mutation looks up its context (e.g. course from course_id) with find(), so an unknown or inaccessible context ID raises RecordNotFound before any module is created.","triggerScenarios":"Calling createModule with a courseId that doesn't exist, belongs to another root account/shard, or that the loader refuses to resolve for an unauthorized user (Canvas loaders raise RecordNotFound for hidden records).","commonSituations":"Hardcoded course IDs copied between environments; IDs from a deleted course; querying from a token whose user cannot see the course (loaders return not-found instead of forbidden).","solutions":["Verify the courseId exists and is visible to the current user via a GraphQL course query first.","Use a Node/context lookup to confirm the ID resolves on the current shard.","If authorization is the real issue, call as a user enrolled in the course (errors are masked as not-found by design).","Handle 'not found' errors in the client and prompt the user to re-select the course."],"exampleFix":"// before\nconst res = await gql(createModuleMutation, { input: { courseId: hardcodedId } })\n// after\nconst course = await gql(getCourseQuery, { id: hardcodedId })\nif (!course?.id) throw new Error('Course not accessible')\nconst res = await gql(createModuleMutation, { input: { courseId: hardcodedId } })","handlingStrategy":"validation","validationCode":"const course = await gql(GET_COURSE, { id: courseId })\nif (!course) throw new Error(`course ${courseId} not found or not visible`)","typeGuard":"function isResolvableCourse(c) { return c != null && typeof c._id === 'string' }","tryCatchPattern":"try {\n  await gql(CREATE_MODULE, { input })\n} catch (e) {\n  if (e.message === 'not found') await promptCourseReselection()\n  else throw e\n}","preventionTips":["Fetch course ids from GraphQL queries, not hardcoded config.","Remember Canvas masks authorization failures as not-found.","Purge ids of deleted courses from stored drafts."],"tags":["graphql","not-found","activerecord","canvas-lms"],"backgroundTag":"record-not-found","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"}