instructure/canvas-lms · error · GraphQL::ExecutionError
relative entry not found
Error message
relative entry not found
What it means
DiscussionEntryLoader#perform rescues ActiveRecord::RecordNotFound raised while building the entries scope and re-raises it as the GraphQL::ExecutionError 'relative entry not found'. This happens when a relative-entry filter references a discussion entry (root_entry_id/relative entry id) that cannot be found in the topic's entries — typically the referenced entry was deleted or belongs to a different topic.
Solutions
- Verify the relative entry id exists and belongs to the same discussion topic (GET the entry first or refetch the topic's entries).
- Re-query without the relative-entry filter to get a fresh cursor, then paginate from there.
- Clear stale client-side cursors/entry ids after moderation or topic edits.
- Use root_entries pagination instead of relative-entry anchoring when entries may be deleted concurrently.
- If you are developing in the loader, consider returning DiscussionEntry.none instead of raising for a missing relative entry to degrade gracefully.
Example fix
// before rescue ActiveRecord::RecordNotFound raise GraphQL::ExecutionError, "relative entry not found" end // after rescue ActiveRecord::RecordNotFound fulfill(object, DiscussionEntry.none) # or re-check the entry id before querying end
Defensive patterns
Strategy: try-catch
Validate before calling
// before paginating with a relative entry
const entry = await fetchDiscussionEntry(entryId)
if (!entry || entry.discussion_topic_id !== topicId || entry.workflow_state === "deleted")
throw new Error("relative entry no longer exists; refetch topic entries") Type guard
const isUsableRelativeEntry = (e) => !!e && e.workflow_state !== "deleted" && e.discussion_topic_id === topicId
Try / catch
try {
const page = await client.request(RELATIVE_ENTRIES_QUERY, { topicId, relativeEntryId })
} catch (e) {
if (e.message === "relative entry not found") {
// refetch first page to obtain a fresh cursor
return client.request(ENTRIES_QUERY, { topicId })
}
throw e
} Prevention
- Don't persist relative-entry cursors across moderation actions
- Validate entry ids against the topic before using as pagination anchors
- Refresh cursors after any entry deletion
- Prefer root-entry pagination when deletions are frequent
When it happens
Trigger: A discussion entries connection query passing a relative entry id (e.g. filter with relativeEntryId / 'after' cursor anchored to an entry) whose DiscussionEntry does not exist in scope_for(object): deleted entry, wrong topic, or fabricated id.
Common situations: Frontends caching entry ids and paginating after an entry a moderator later deleted; passing an entry id from one discussion into another topic's entriesConnection; clients decoding cursors from older data after entry cleanup.
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
- A course with that id does not exist
- ActiveRecord::RecordNotFound
- Allocation rule not found
- An assignment with that id does not exist
- Assignment not found
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/ce520b2ce5aef86a.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/loaders/discussion_entry_loader.rb:86
scope.active.where(UserSearch.like_condition("message"), pattern: UserSearch.like_string_for(@search_term))
end
end
if @relative_entry_id
relative_entry = scope.find(@relative_entry_id)
condition = @before_entry ? "<" : ">"
condition += "=" if @include_entry
scope = scope.where("created_at #{condition}?", relative_entry.created_at)
end
# unread filter is used like search results and need to exclude deleted entries
scope = scope.active.unread_for_user_before(@current_user, @unread_before) if @filter == "unread"
scope = scope.where(workflow_state: "deleted") if @filter == "deleted"
scope = scope.where(user_id: @user_search_id) unless @user_search_id.nil?
scope = scope.preload(:user, :editor)
fulfill(object, scope)
rescue ActiveRecord::RecordNotFound
raise GraphQL::ExecutionError, "relative entry not found"
end
end
def scope_for(object)
if object.is_a?(DiscussionTopic)
object.discussion_entries
elsif object.is_a?(DiscussionEntry)
if object.root_entry_id.nil?
if @user_search_id
object.flattened_discussion_subentries
else
object.root_discussion_replies
end
else
object.discussion_subentries
end
end
endView on GitHub (pinned to 1c9f0bb801)