{"record":{"id":"e52573406bc5e6e3","repo":"instructure/canvas-lms","slug":"group-not-found","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.rb","lineNumber":44,"sourceCode":"  def resolve(input:)\n    outcome_group = learning_outcome_group(input)\n\n    outcome_input = attrs(input, outcome_group)\n\n    record = LearningOutcome.new(context: outcome_group.context, **outcome_input)\n    record.saving_user = current_user\n    check_permission(record)\n    return errors_for(record) unless record.save\n\n    outcome_group.add_outcome(record)\n    { learning_outcome: record }\n  end\n\n  private\n\n  def learning_outcome_group(input)\n    LearningOutcomeGroup.active.find_by(id: input[:group_id]).tap do |group|\n      raise GraphQL::ExecutionError, I18n.t(\"group not found\") if group.nil?\n    end\n  end\n\n  def check_permission(outcome)\n    raise GraphQL::ExecutionError, I18n.t(\"insufficient permission\") unless outcome.grants_right? current_user, :create\n  end\nend\n","sourceCodeStart":26,"sourceCodeEnd":52,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/create_learning_outcome.rb#L26-L52","documentation":"In CreateLearningOutcome, the private helper learning_outcome_group looks up LearningOutcomeGroup.active.find_by(id: input[:group_id]) and raises GraphQL::ExecutionError 'group not found' when no active group matches. 'Active' means not soft-deleted (workflow_state not deleted), so soft-deleted groups also produce this error even though the row exists.","triggerScenarios":"Calling createLearningOutcome with group_id that is: nil/omitted, a malformed ID, an ID from another shard/root account, or the ID of a soft-deleted LearningOutcomeGroup.","commonSituations":"Client caches a group ID that was later deleted; copying group IDs between test and production Canvas; forgetting that global outcome groups vs context groups have different ID spaces; passing a string instead of an ID type is coerced to nil.","solutions":["Verify the group_id refers to an existing, non-deleted LearningOutcomeGroup (check workflow_state = 'active').","Re-fetch the group via the GraphQL query before mutating, and use the fresh ID.","Confirm you are calling on the same shard/root account where the group lives.","Ensure the client sends the group_id argument (required by this mutation's flow) and it parses as a valid ID."],"exampleFix":"// before\ncreateLearningOutcome(input: { title, groupOfId: staleId })\n// after\nconst group = await fetchGroupById(staleId) // verify active\nconst id = group?.workflowState === 'active' ? group._id : await createOrPickGroup()\ncreateLearningOutcome(input: { title, groupId: id })","handlingStrategy":"validation","validationCode":"const group = await gql(GET_OUTCOME_GROUP, { id: groupId })\nif (!group || group.workflowState !== 'active') throw new Error(`outcome group ${groupId} not found/deleted`)","typeGuard":"function isActiveGroup(g) { return g != null && g.workflowState === 'active' && typeof g._id === 'string' }","tryCatchPattern":"try {\n  await gql(CREATE_OUTCOME, { input })\n} catch (e) {\n  if (e.message === 'group not found') await reselectGroupAndRetry()\n  else throw e\n}","preventionTips":["Never cache LearningOutcomeGroup ids across environments.","Refresh group lists before mutations instead of using stale ids.","Treat deleted groups (workflow_state 'deleted') as gone in UI state."],"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"}