instructure/canvas-lms · error · GraphQL::ExecutionError
unauthorized
Error message
unauthorized
What it means
SubscribeToDiscussionTopic raises GraphQL::ExecutionError "unauthorized" when attempting to subscribe (input[:subscribed] == true) to a non-announcement topic whose subscription_hold(current_user, session) returns a truthy reason (app/graphql/mutations/subscribe_to_discussion_topic.rb:31). subscription_hold encodes business rules — e.g. topic locked, user not allowed to reply/participate — that forbid subscribing.
Solutions
- Only send subscribed: true for topics that are open for participation (subscription_hold is null).
- Check subscription_hold state client-side before calling the mutation.
- Handle "unauthorized" by showing the topic's hold/reason message in the UI.
- If subscribing to an announcement, note announcements are exempt but generally manage their own subscription semantics.
Example fix
// before
await subscribe({ discussionTopicId, subscribed: true })
// after
if (!topic.subscriptionHold) {
await subscribe({ discussionTopicId, subscribed: true })
} Defensive patterns
Strategy: validation
Validate before calling
if (subscribed && !topic.isAnnouncement && topic.subscriptionHold) throw new SkipMutation(`subscription held: ${topic.subscriptionHold}`) Try / catch
try { await mutate(...) } catch (e) { if (e.message === 'unauthorized') showSubscriptionHoldReason(); else throw e; } Prevention
- Check subscriptionHold before enabling subscribe buttons.
- Disable subscribing on locked or closed topics.
- Distinguish announcements (exempt from the hold check) from regular topics.
When it happens
Trigger: Calling the mutation with subscribed: true on a regular (non-announcement) discussion where subscription_hold returns a hold message, e.g. the topic is locked/closed for comments or the user lacks participation rights.
Common situations: Subscribing to a topic whose comments are closed; users in read-only roles trying to subscribe; topics inside locked modules; attempting to subscribe after the availability window ended.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- not found
- Cannot set rubric self assessment for discussion assignments
- Insufficient Permissions
- Insufficient permissions
- not authorized
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/38a1494bc40de304.
Report an issue: GitHub.
Appendix: source
Thrown at app/graphql/mutations/subscribe_to_discussion_topic.rb:31
# 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)