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
- 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.
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
- 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.
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
- unauthorized
- Cannot set rubric self assessment for discussion assignments
- # not found
- Insufficient Permissions
- Insufficient permissions
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)