instructure/canvas-lms · error · ActiveRecord::RecordNotFound

not found

Error message

not found

What it means

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.

Solutions

  1. Call the mutation only for discussion topics the user can read (verify via the discussion topic GraphQL query first).
  2. Confirm the ID belongs to a course the current user is enrolled in.
  3. Check for section/availability restrictions on the topic.
  4. Handle "not found" in the client as an authorization issue and redirect the user.

Example fix

// before
await client.mutate({ mutation: SUBSCRIBE, variables: { discussionTopicId, subscribed: true } })
// after
const topic = await client.query({ query: GET_TOPIC, variables: { id: discussionTopicId } }).catch(() => null)
if (topic) {
  await client.mutate({ mutation: SUBSCRIBE, variables: { discussionTopicId, subscribed: true } })
}
Defensive patterns

Strategy: validation

Validate before calling

const topic = await client.query({ query: GET_TOPIC, variables: { id: discussionTopicId } }).catch(() => null); if (!topic) throw new SkipMutation('topic not readable by this user')

Try / catch

try { await mutate(...) } catch (e) { if (e.message === 'not found') showAccessDeniedNotice(); else throw e; }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

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


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/08adc83f9b7ece54. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/subscribe_to_discussion_topic.rb:30

# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# 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::SubscribeToDiscussionTopic < Mutations::BaseMutation
  graphql_name "SubscribeToDiscussionTopic"

  argument :discussion_topic_id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("DiscussionTopic")
  argument :subscribed, Boolean, required: true

  field :discussion_topic, Types::DiscussionType, null: false
  def resolve(input:)
    discussion_topic = DiscussionTopic.find(input[:discussion_topic_id])
    raise ActiveRecord::RecordNotFound unless discussion_topic.grants_right?(current_user, session, :read)
    raise GraphQL::ExecutionError, "unauthorized" if input[:subscribed] && !discussion_topic.is_announcement && discussion_topic.subscription_hold(current_user, session)

    discussion_topic.change_subscribed_state(input[:subscribed], current_user)

    {
      discussion_topic:
    }
  rescue ActiveRecord::RecordNotFound
    raise GraphQL::ExecutionError, "not found"
  end
end

View on GitHub (pinned to 1c9f0bb801)