{"record":{"id":"08adc83f9b7ece54","repo":"instructure/canvas-lms","slug":"not-found-subscribe-to-discussion-topic","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"exception","errorClass":"ActiveRecord::RecordNotFound","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/subscribe_to_discussion_topic.rb","lineNumber":30,"sourceCode":"# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass Mutations::SubscribeToDiscussionTopic < Mutations::BaseMutation\n  graphql_name \"SubscribeToDiscussionTopic\"\n\n  argument :discussion_topic_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func(\"DiscussionTopic\")\n  argument :subscribed, Boolean, required: true\n\n  field :discussion_topic, Types::DiscussionType, null: false\n  def resolve(input:)\n    discussion_topic = DiscussionTopic.find(input[:discussion_topic_id])\n    raise ActiveRecord::RecordNotFound unless discussion_topic.grants_right?(current_user, session, :read)\n    raise GraphQL::ExecutionError, \"unauthorized\" if input[:subscribed] && !discussion_topic.is_announcement && discussion_topic.subscription_hold(current_user, session)\n\n    discussion_topic.change_subscribed_state(input[:subscribed], current_user)\n\n    {\n      discussion_topic:\n    }\n  rescue ActiveRecord::RecordNotFound\n    raise GraphQL::ExecutionError, \"not found\"\n  end\nend\n","sourceCodeStart":12,"sourceCodeEnd":42,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/subscribe_to_discussion_topic.rb#L12-L42","documentation":"SubscribeToDiscussionTopic raises ActiveRecord::RecordNotFound directly when discussion_topic.grants_right?(current_user, session, :read) is false (app/graphql/mutations/subscribe_to_discussion_topic.rb:30). Hiding the record behind RecordNotFound avoids leaking existence of unreadable topics; the rescue at the end converts it to the generic \"not found\" GraphQL error.","triggerScenarios":"Calling the subscribeToDiscussionTopic mutation with a valid discussion topic ID that exists, but the acting user lacks :read on it (not enrolled, topic in a course they can't access, or section-limited topic).","commonSituations":"Using an ID from another course or account; a student referencing a topic hidden via section restrictions; a token/user change making an old cached ID unreadable; enrollment removed while the page stayed open.","solutions":["Call the mutation only for discussion topics the user can read (verify via the discussion topic GraphQL query first).","Confirm the ID belongs to a course the current user is enrolled in.","Check for section/availability restrictions on the topic.","Handle \"not found\" in the client as an authorization issue and redirect the user."],"exampleFix":"// before\nawait client.mutate({ mutation: SUBSCRIBE, variables: { discussionTopicId, subscribed: true } })\n// after\nconst topic = await client.query({ query: GET_TOPIC, variables: { id: discussionTopicId } }).catch(() => null)\nif (topic) {\n  await client.mutate({ mutation: SUBSCRIBE, variables: { discussionTopicId, subscribed: true } })\n}","handlingStrategy":"validation","validationCode":"const topic = await client.query({ query: GET_TOPIC, variables: { id: discussionTopicId } }).catch(() => null); if (!topic) throw new SkipMutation('topic not readable by this user')","typeGuard":null,"tryCatchPattern":"try { await mutate(...) } catch (e) { if (e.message === 'not found') showAccessDeniedNotice(); else throw e; }","preventionTips":["Only offer subscribe controls on topics the user has already read via the API.","Re-check access after enrollment changes.","Treat 'not found' on valid-looking IDs as an access problem, not a bug."],"tags":["graphql","authorization","record-not-found","discussions","canvas"],"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"}