{"record":{"id":"ce520b2ce5aef86a","repo":"instructure/canvas-lms","slug":"relative-entry-not-found","errorCode":null,"errorMessage":"relative entry not found","messagePattern":"relative entry not found","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/loaders/discussion_entry_loader.rb","lineNumber":86,"sourceCode":"                  scope.active.where(UserSearch.like_condition(\"message\"), pattern: UserSearch.like_string_for(@search_term))\n                end\n      end\n\n      if @relative_entry_id\n        relative_entry = scope.find(@relative_entry_id)\n        condition = @before_entry ? \"<\" : \">\"\n        condition += \"=\" if @include_entry\n        scope = scope.where(\"created_at #{condition}?\", relative_entry.created_at)\n      end\n\n      # unread filter is used like search results and need to exclude deleted entries\n      scope = scope.active.unread_for_user_before(@current_user, @unread_before) if @filter == \"unread\"\n      scope = scope.where(workflow_state: \"deleted\") if @filter == \"deleted\"\n      scope = scope.where(user_id: @user_search_id) unless @user_search_id.nil?\n      scope = scope.preload(:user, :editor)\n      fulfill(object, scope)\n    rescue ActiveRecord::RecordNotFound\n      raise GraphQL::ExecutionError, \"relative entry not found\"\n    end\n  end\n\n  def scope_for(object)\n    if object.is_a?(DiscussionTopic)\n      object.discussion_entries\n    elsif object.is_a?(DiscussionEntry)\n      if object.root_entry_id.nil?\n        if @user_search_id\n          object.flattened_discussion_subentries\n        else\n          object.root_discussion_replies\n        end\n      else\n        object.discussion_subentries\n      end\n    end\n  end","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/loaders/discussion_entry_loader.rb#L68-L104","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nrescue ActiveRecord::RecordNotFound\n  raise GraphQL::ExecutionError, \"relative entry not found\"\nend\n// after\nrescue ActiveRecord::RecordNotFound\n  fulfill(object, DiscussionEntry.none) # or re-check the entry id before querying\nend","handlingStrategy":"try-catch","validationCode":"// before paginating with a relative entry\nconst entry = await fetchDiscussionEntry(entryId)\nif (!entry || entry.discussion_topic_id !== topicId || entry.workflow_state === \"deleted\")\n  throw new Error(\"relative entry no longer exists; refetch topic entries\")","typeGuard":"const isUsableRelativeEntry = (e) => !!e && e.workflow_state !== \"deleted\" && e.discussion_topic_id === topicId","tryCatchPattern":"try {\n  const page = await client.request(RELATIVE_ENTRIES_QUERY, { topicId, relativeEntryId })\n} catch (e) {\n  if (e.message === \"relative entry not found\") {\n    // refetch first page to obtain a fresh cursor\n    return client.request(ENTRIES_QUERY, { topicId })\n  }\n  throw e\n}","preventionTips":["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"],"tags":["graphql","loader","pagination","record-not-found"],"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"}