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
- Refetch the conversation list and use a current conversation_id, discarding stale cached ids.
- Handle the 'not found' GraphQL error client-side by refreshing the inbox and informing the user the conversation no longer exists.
- Confirm the id belongs to the same environment and to the authenticated user's conversations.
- Retry after refresh only if the conversation still exists; otherwise drop the draft or move it to the sender's drafts.
- 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
- Don't cache conversation ids long-term; re-fetch before sending
- Handle concurrent deletion across devices
- Scope lookups through the current user's conversations server-side
- Surface drafts so a failed send isn't lost
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)