{"record":{"id":"53a7e9b6ca568cfa","repo":"instructure/canvas-lms","slug":"group-not-found-create-learning-outcome","errorCode":null,"errorMessage":"Group not found","messagePattern":"Group not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/create_learning_outcome_group.rb","lineNumber":49,"sourceCode":"  def resolve(input:)\n    @outcome_group = get_group(input[:id])\n\n    check_user_permissions\n\n    @child_outcome_group = @outcome_group.child_outcome_groups.build(attributes(input))\n    @child_outcome_group.saving_user = current_user\n    if @child_outcome_group.save\n      { learning_outcome_group: @child_outcome_group }\n    else\n      errors_for(@child_outcome_group)\n    end\n  end\n\n  private\n\n  def get_group(id)\n    LearningOutcomeGroup.active.find_by(id:).tap do |group|\n      raise GraphQL::ExecutionError, I18n.t(\"Group not found\") unless group\n    end\n  end\n\n  def check_user_permissions\n    raise GraphQL::ExecutionError, I18n.t(\"Insufficient permissions\") unless can_manage_outcomes\n  end\n\n  def can_manage_outcomes\n    if @outcome_group.context\n      @outcome_group.context.grants_right?(current_user, session, :manage_outcomes)\n    else\n      Account.site_admin.grants_right?(current_user, session, :manage_global_outcomes)\n    end\n  end\n\n  def attributes(input)\n    input.to_h.slice(:title, :description, :vendor_guid)\n  end","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/create_learning_outcome_group.rb#L31-L67","documentation":"CreateLearningOutcomeGroup#get_group looks up LearningOutcomeGroup.active.find_by(id:) and raises 'Group not found' when nil. Like error 151, only active (non-deleted) groups count; missing, deleted, or cross-shard IDs all fail.","triggerScenarios":"Calling createLearningOutcomeGroup with parent_outcome_group_id pointing to a nonexistent, soft-deleted, or other-root-account group; omitting the id when the mutation requires nesting under an existing group.","commonSituations":"UI kept a stale parent group after deletion; syncing groups across environments; passing a course-level group ID where an account-level one is expected.","solutions":["Confirm the group ID exists and workflow_state is 'active' via a prior GraphQL lookup.","Re-select the parent group in the UI instead of relying on cached IDs.","Ensure the request is executed on the correct shard for the group.","Omit the parent id only if the mutation supports root-level creation for your context; otherwise always pass a verified id."],"exampleFix":"// before\nawait gql(createLearningOutcomeGroup, { input: { parentGroupId: cachedId } })\n// after\nconst parent = await gql(getOutcomeGroup, { id: cachedId })\nif (!parent || parent.workflowState !== 'active') throw new Error('Pick a valid parent group')\nawait gql(createLearningOutcomeGroup, { input: { parentGroupId: cachedId } })","handlingStrategy":"validation","validationCode":"const parent = groupId ? await gql(GET_OUTCOME_GROUP, { id: groupId }) : null\nif (groupId && (!parent || parent.workflowState !== 'active')) throw new Error('parent group not found')","typeGuard":"function isResolvableActiveGroup(g) { return g != null && g.workflowState === 'active' }","tryCatchPattern":"try {\n  await gql(CREATE_OUTCOME_GROUP, { input })\n} catch (e) {\n  if (e.message === 'Group not found') openGroupPicker()\n  else throw e\n}","preventionTips":["Resolve parent groups through GraphQL Node lookups right before mutation.","Keep UI selection in sync with group deletions (subscribe or refetch).","Validate shard/account scope of ids in cross-account tools."],"tags":["graphql","not-found","canvas-lms","learning-outcomes"],"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"}