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
- Verify the course_id exists (e.g. GET /api/v1/courses/:id) and correct the id passed to the mutation
- Confirm the request is routed to the correct shard/root account for that course id
- If the course was deleted, restore it or target a different course; deleted courses cannot receive post policies
- 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
- Always pass the numeric Canvas course id, never sis ids, slugs, or names
- Fetch the course id from the API/canvas environment rather than hardcoding across environments
- Handle deleted courses: re-check course existence after any deletion workflow
- In sharded setups, keep GraphQL calls on the same root account/shard as the course
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
- Course not found
- No such context for #
- and cannot be used together
- ActiveRecord::RecordNotFound
- Allocation rule not found
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)