{"record":{"id":"2931394bb854139e","repo":"instructure/canvas-lms","slug":"a-and-b-cannot-be-used-together","errorCode":null,"errorMessage":"{a} and {b} cannot be used together","messagePattern":"(.+?) and (.+?) cannot be used together","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/hide_assignment_grades.rb","lineNumber":50,"sourceCode":"\n  def resolve(input:)\n    begin\n      assignment = AbstractAssignment.find_assignment_or_peer_review(input[:assignment_id])\n      course = assignment.context\n      sections = input[:section_ids] ? course.course_sections.where(id: input[:section_ids]) : nil\n    rescue ActiveRecord::RecordNotFound\n      raise GraphQL::ExecutionError, \"not found\"\n    end\n\n    verify_authorized_action!(assignment, :grade)\n\n    unless assignment.grades_published?\n      raise GraphQL::ExecutionError, \"Assignments under moderation cannot be hidden before grades are published\"\n    end\n    raise GraphQL::ExecutionError, \"Anonymous assignments cannot be posted by section\" if sections && assignment.anonymous_grading?\n\n    if input[:only_student_ids] && input[:skip_student_ids]\n      raise GraphQL::ExecutionError, I18n.t(\"{a} and {b} cannot be used together\", a: \"only_student_ids\", b: \"skip_student_ids\")\n    end\n\n    visible_enrollments = course.apply_enrollment_visibility(course.student_enrollments, current_user, sections)\n    visible_enrollments = visible_enrollments.where(user_id: input[:only_student_ids]) if input[:only_student_ids]\n    visible_enrollments = visible_enrollments.where.not(user_id: input[:skip_student_ids]) if input[:skip_student_ids]\n\n    submissions_scope = assignment.submissions.active.joins(user: :enrollments)\n    submissions_scope = course.apply_enrollment_visibility(submissions_scope, current_user).merge(visible_enrollments)\n    progress = course.progresses.new(tag: \"hide_assignment_grades\")\n\n    if progress.save\n      progress.process_job(\n        assignment,\n        :hide_submissions,\n        { preserve_method_args: true, priority: Delayed::HIGH_PRIORITY },\n        progress:,\n        submission_ids: submissions_scope.pluck(:id),\n        skip_content_participation_refresh: false","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/hide_assignment_grades.rb#L32-L68","documentation":"hideAssignmentGrades rejects simultaneous use of the mutually exclusive inputs only_student_ids and skip_student_ids; the I18n template {a} and {b} cannot be used together interpolates the argument names, so the surfaced message reads 'only_student_ids and skip_student_ids cannot be used together'.","triggerScenarios":"Calling the mutation with both onlyStudentIds and skipStudentIds non-nil in the same input object.","commonSituations":"Client form state where both filter fields retain values from previous edits; generic wrapper functions that forward all variables including null-vs-omitted confusion; assembling variables programmatically from a filter object.","solutions":["Send only one of the two fields; omit (don't send) the other entirely.","In client code, build variables conditionally so the two filters can never coexist.","Reset the opposite filter in the UI when one is chosen.","If both semantics are needed (hide some, skip others), compute the final student id list client-side and pass a single only_student_ids."],"exampleFix":"// before\nmutate({ variables: { assignmentId, onlyStudentIds: ids, skipStudentIds: skips } });\n// after\nconst vars = { assignmentId };\nif (ids?.length) vars.onlyStudentIds = ids;\nelse if (skips?.length) vars.skipStudentIds = skips;\nmutate({ variables: vars });","handlingStrategy":"validation","validationCode":"if (onlyStudentIds?.length && skipStudentIds?.length) {\n  throw new ValidationError('only_student_ids and skip_student_ids are mutually exclusive');\n}","typeGuard":null,"tryCatchPattern":"try { await hideGrades(vars); }\ncatch (e) {\n  if (String(e).includes('cannot be used together')) {\n    delete vars.skipStudentIds; // keep only one filter\n    return hideGrades(vars);\n  }\n  throw e;\n}","preventionTips":["Build mutation variables with an explicit builder function, not spread of raw filter state","Clear the opposite filter in the UI when one is set","Add a unit test asserting both filters never coexist","Prefer a single resolved student-id list when complex filtering is needed"],"tags":["graphql","mutually-exclusive","input-validation","canvas-lms"],"backgroundTag":"mutually-exclusive-options","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"}