instructure/canvas-lms · error · GraphQL::ExecutionError

Unable to find Discussion Topic

Error message

Unable to find Discussion Topic

What it means

GraphQL::ExecutionError raised in Mutations::DeleteDiscussionTopic#resolve when DiscussionTopic.active.find_by(id: input[:id]) returns nil or the record fails the :read rights check. Like delete_discussion_entry, authorization failures are masked as 'Unable to find Discussion Topic' to avoid revealing record existence.

Solutions

  1. Confirm the topic exists and is active: DiscussionTopic.active.exists?(id)
  2. Verify the caller has :read and :delete rights on the topic's course
  3. Refresh the client's topic list instead of retrying with cached ids
  4. Call as an admin (or with appropriate course permissions) for moderation deletes

Example fix

// before
deleteDiscussionTopic(id) // topic deleted elsewhere -> 'Unable to find Discussion Topic'
// after
const topic = topics.find(t => t.id === id)
if (topic) await deleteDiscussionTopic(id)
else refreshTopics()
Defensive patterns

Strategy: try-catch

Validate before calling

const exists = topics.some(t => t.id === id) && topic.grantsRight(currentUser, 'delete')
if (!exists) skipDelete()

Try / catch

try {
  await deleteDiscussionTopic(id)
} catch (e) {
  if (/Unable to find Discussion Topic/.test(e.message)) refreshTopics()
}

Prevention

When it happens

Trigger: Deleting a discussion topic with a nonexistent id, an id of an already-deleted (non-active) topic, or a topic the user cannot read (no enrollment/permissions) — all collapse into this single message.

Common situations: Stale topic ids in the UI after a topic was deleted by another teacher; students attempting to delete topics they cannot read; id encoding mismatches between REST and GraphQL surfaces.

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/9fb9d79974babdba. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/delete_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::DeleteDiscussionTopic < Mutations::BaseMutation
  graphql_name "DeleteDiscussionTopic"
  # input arguments
  argument :id, ID, required: true, prepare: GraphQLHelpers.relay_or_legacy_id_prepare_func("DiscussionTopic")

  # the return data if the delete is successful
  field :discussion_topic_id, ID, null: false

  def resolve(input:)
    record = DiscussionTopic.active.find_by(id: input[:id])
    raise GraphQL::ExecutionError, "Unable to find Discussion Topic" if record.nil? || !record.grants_right?(current_user, nil, :read)

    unless record.grants_right?(current_user, nil, :delete)
      raise GraphQL::ExecutionError, "Insufficient permissions"
    end

    context[:deleted_models] = { discussion_topic: {} }
    context[:deleted_models][:discussion_topic] = record
    record.destroy
    {
      discussion_topic_id: record.id
    }
  end

  def self.discussion_topic_id_log_entry(_topic, context)
    context[:deleted_models][:discussion_topic]
  end
end

View on GitHub (pinned to 1c9f0bb801)