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

not found

Error message

not found

What it means

AddConversationMessage#resolve rescues ActiveRecord::RecordNotFound and raises the terse 'not found' GraphQL::ExecutionError. Inside resolve, conversation or participant lookups (e.g. Conversation.find / participant verification for conversation_id) raise RecordNotFound when the id does not exist or is not visible to the current user, so the whole mutation fails with this generic message.

Solutions

  1. Refetch the conversation list and use a current conversation_id, discarding stale cached ids.
  2. Handle the 'not found' GraphQL error client-side by refreshing the inbox and informing the user the conversation no longer exists.
  3. Confirm the id belongs to the same environment and to the authenticated user's conversations.
  4. Retry after refresh only if the conversation still exists; otherwise drop the draft or move it to the sender's drafts.
  5. In code, pre-check visibility: scope lookups through the user's visible conversations instead of a bare find.

Example fix

// before
rescue ActiveRecord::RecordNotFound
  raise GraphQL::ExecutionError, "not found"
// after
rescue ActiveRecord::RecordNotFound
  raise GraphQL::ExecutionError, "conversation not found or not visible to current user"
end
// caller:
try { await addConversationMessage(id) } catch (e) { if (/not found/.test(e.message)) refreshInbox() }
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the conversation is still visible before sending
const conv = await fetchConversation(conversationId) // from the user's own conversation list
if (!conv) throw new Error("conversation no longer exists; refresh inbox before replying")

Type guard

const isAccessibleConversation = (c) => !!c && c.visible !== false && c.audience?.includes(currentUser.id)

Try / catch

try {
  await client.request(ADD_MESSAGE_MUTATION, { conversationId, body })
} catch (e) {
  if (e.message === "not found") {
    await refreshInbox()
    return notify("This conversation is no longer available")
  }
  throw e
}

Prevention

When it happens

Trigger: Mutation addConversationMessage called with a conversation_id that doesn't exist, was deleted from the inbox, belongs to another user, or is malformed; also a conversation record purged between listing and message send.

Common situations: Mobile clients caching conversation ids and sending to a conversation archived/deleted elsewhere; two devices racing (one deletes the thread, the other replies); scripts iterating stale conversation id lists; ids copied across environments.

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

Appendix: source

Thrown at app/graphql/mutations/add_conversation_message.rb:65

      message_ids: input[:included_messages],
      body: input[:body],
      attachment_ids: input[:attachment_ids],
      domain_root_account_id: context[:domain_root_account].id,
      media_comment_id: input[:media_comment_id],
      media_comment_type: input[:media_comment_type]
    )
    InstStatsd::Statsd.distributed_increment("inbox.message.sent.isReply.react")
    InstStatsd::Statsd.distributed_increment("inbox.message.sent.react")
    InstStatsd::Statsd.count("inbox.message.sent.recipients.react", message[:recipients_count])
    if input[:media_comment_id] || ConversationMessage.where(id: message[:message]&.id).first&.has_media_objects
      InstStatsd::Statsd.distributed_increment("inbox.message.sent.media.react")
    end
    if !message[:message].nil? && message[:message][:attachment_ids].present?
      InstStatsd::Statsd.distributed_increment("inbox.message.sent.attachment.react")
    end
    { conversation_message: message[:message] }
  rescue ActiveRecord::RecordNotFound
    raise GraphQL::ExecutionError, "not found"
  rescue ActiveRecord::RecordInvalid => e
    errors_for(e.record)
  rescue ConversationsHelper::Error => e
    validation_error(e.message)
  end

  def get_conversation(id)
    conversation = current_user.all_conversations.find_by(conversation_id: id)
    raise ActiveRecord::RecordNotFound unless conversation

    conversation
  end
end

View on GitHub (pinned to 1c9f0bb801)