instructure/canvas-lms · error · GraphQL::ExecutionError

A course with that id does not exist

Error message

A course with that id does not exist

What it means

Raised by the SetCoursePostPolicy GraphQL mutation when Course.find(input[:course_id]) raises ActiveRecord::RecordNotFound, i.e. no Course exists with the supplied id. The mutation rescues the Rails exception and re-raises it as a GraphQL::ExecutionError with a stable client-facing message so callers get a clean GraphQL error instead of an unhandled record-not-found.

Solutions

  1. Verify the course_id exists (e.g. GET /api/v1/courses/:id) and correct the id passed to the mutation
  2. Confirm the request is routed to the correct shard/root account for that course id
  3. If the course was deleted, restore it or target a different course; deleted courses cannot receive post policies
  4. Use the course's actual numeric id rather than a slug, sis id, or lti id — convert sis ids via the sis_course_id API lookups if needed

Example fix

// before: unverified id
setCoursePostPolicy(input: { courseId: params.courseRef, ... })

// after: resolve and validate the course first
const course = await canvas.get(`/api/v1/courses/${params.courseRef}`);
if (!course?.id) throw new Error(`course not found: ${params.courseRef}`);
setCoursePostPolicy(input: { courseId: course.id, ... });
Defensive patterns

Strategy: validation

Validate before calling

async function requireCourse(courseId) {
  const res = await fetch(`/api/v1/courses/${courseId}`);
  if (!res.ok) throw new Error(`Course ${courseId} does not exist (HTTP ${res.status})`);
  return res.json();
}

Type guard

const isCourseRef = (v) =>
  (typeof v === 'number' && Number.isInteger(v) && v > 0) ||
  (typeof v === 'string' && /^\d+$/.test(v));

Try / catch

try {
  await setCoursePostPolicy({ variables: { courseId, ... } });
} catch (e) {
  if (e.message === 'A course with that id does not exist') {
    redirectToCoursePicker();
  } else { throw e; }
}

Prevention

When it happens

Trigger: Executing the setCoursePostPolicy mutation with a course_id that (a) never existed, (b) was deleted (soft/hard delete removing it from the default scope), (c) is a non-integer/garbage id causing find to fail, or (d) lives on a different shard than the one the request is scoped to.

Common situations: Stale course ids cached in a frontend after a course deletion; copy-pasted IDs from another Canvas environment (test vs production); cross-shard id handling mistakes in Canvas's Switchman-sharded setup; typos or id/name confusion when scripting API calls.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/968381ac518d79ce. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/set_course_post_policy.rb:33

# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class Mutations::SetCoursePostPolicy < Mutations::BaseMutation
  graphql_name "SetCoursePostPolicy"

  argument :course_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("Course")
  argument :post_manually, Boolean, required: true

  field :post_policy, Types::PostPolicyType, null: true

  def resolve(input:)
    begin
      course = Course.find(input[:course_id])
    rescue ActiveRecord::RecordNotFound
      raise GraphQL::ExecutionError, "A course with that id does not exist"
    end

    # checking if the current user has manage_course_content_edit permission in the current course
    perms = %i[manage_course_content_edit manage_grades]
    verify_any_authorized_actions!(course, perms)

    course.apply_post_policy!(post_manually: input[:post_manually])
    { post_policy: course.default_post_policy }
  end

  def self.post_policy_log_entry(post_policy, _context)
    post_policy.course
  end
end

View on GitHub (pinned to 1c9f0bb801)