{"record":{"id":"738871a92fba94f9","repo":"instructure/canvas-lms","slug":"not-found-create-discussion-entry","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"exception","errorClass":"ActiveRecord::RecordNotFound","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/create_discussion_entry.rb","lineNumber":36,"sourceCode":"# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass Mutations::CreateDiscussionEntry < Mutations::BaseMutation\n  graphql_name \"CreateDiscussionEntry\"\n\n  argument :discussion_topic_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"DiscussionTopic\")\n  argument :file_id, ID, required: false, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"Attachment\")\n  argument :message, String, required: true\n  argument :parent_entry_id, ID, required: false, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"DiscussionEntry\")\n\n  argument :is_anonymous_author, Boolean, required: false\n  argument :quoted_entry_id, ID, required: false, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"DiscussionEntry\")\n\n  field :discussion_entry, Types::DiscussionEntryType, null: true\n  field :my_sub_assignment_submissions, [Types::SubmissionType], null: true\n  def resolve(input:)\n    topic = DiscussionTopic.find(input[:discussion_topic_id])\n    raise ActiveRecord::RecordNotFound unless topic.grants_right?(current_user, session, :read)\n\n    # if the user is writing a threaded reply when the allow threaded replies feature is disabled\n    if !topic.threaded? && !input[:parent_entry_id].nil?\n      return validation_error(I18n.t(\"Threaded replies are not allowed in this context\"))\n    end\n\n    association = topic.discussion_entries\n    entry = build_entry(association, input[:message], topic, !!input[:is_anonymous_author])\n\n    if input[:parent_entry_id]\n      parent_entry = topic.discussion_entries.find(input[:parent_entry_id])\n      entry.parent_entry = parent_entry\n    end\n\n    if input[:quoted_entry_id] && DiscussionEntry.find(input[:quoted_entry_id])\n      entry.quoted_entry_id = input[:quoted_entry_id]\n    end\n","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/create_discussion_entry.rb#L18-L54","documentation":"This GraphQL mutation converts a rescued ActiveRecord::RecordNotFound into a GraphQL::ExecutionError with message \"not found\". In resolve, the topic lookup (DiscussionTopic.find) or the read-permission check raises RecordNotFound when the discussion topic ID does not exist or the current user may not read it. The mutation deliberately flattens both cases into one client-facing error.","triggerScenarios":"Calling mutation createDiscussionEntry with a discussion_topic_id that does not exist, an already-deleted topic, or a topic the user lacks :read rights on (also parent_entry_id/quoted_entry_id lookups that fail later in resolve).","commonSituations":"Frontend caching a topic id after the topic was deleted; cross-shard or cross-course id reuse; a student passing another course's topic id; stale Relay global ids after data resets in test environments.","solutions":["Verify the discussion_topic_id exists via the discussionTopic query before submitting the entry.","Confirm the current user can read the topic (enrollment active, topic not ungraded-and-hidden, correct context).","Refresh the page/topic list to pick up a new topic id after deletions or migrations.","Check for shard/context mismatch: ids must belong to the same root account shard."],"exampleFix":"// before: blindly using a cached id\ncreateDiscussionEntry({discussionTopicId: cachedTopicId, message})\n\n// after: confirm topic first\nconst topic = await fetchDiscussionTopic(cachedTopicId)\nif (!topic) { redirectToDiscussionList(); return }\nawait createDiscussionEntry({discussionTopicId: topic.id, message})","handlingStrategy":"try-catch","validationCode":"const topic = await client.query({query: TOPIC_QUERY, variables: {id: topicId}})\nif (!topic?.data?.legacyNode) throw new Error('topic does not exist')","typeGuard":null,"tryCatchPattern":"try {\n  await createDiscussionEntry({discussionTopicId, message})\n} catch (e) {\n  if (/not found/i.test(e.message)) showFlash('Discussion not found or unavailable.')\n  else throw e\n}","preventionTips":["Always resolve topic ids from a live query, never long-lived caches.","Check topic permissions before rendering the composer.","Handle deletion events (subscriptions/polling) by invalidating open reply forms."],"tags":["graphql","record-not-found","discussions","permissions"],"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"}