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

  1. Only send subscribed: true for topics that are open for participation (subscription_hold is null).
  2. Check subscription_hold state client-side before calling the mutation.
  3. Handle "unauthorized" by showing the topic's hold/reason message in the UI.
  4. 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

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.

Related errors


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)