instructure/canvas-lms · warning
Document index status sync failed for ai_experience #
Error message
Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message} What it means
In AiExperiences::ConversationContextDocumentsService#sync_index_status, a LlmConversation::Errors::ConversationError raised while syncing document index status is caught and only logged as a warning; no exception propagates. The log line 'Document index status sync failed ...' means the sync against the LLM conversation service failed (service error), so the AI experience's document index status may be stale. It is a swallowed, non-fatal sync failure, not a crash.
Solutions
- Check the Rails logs for the full warn message and the embedded e.message to identify the underlying LlmConversation::Errors::ConversationError cause.
- Verify the ai_experience's llm_conversation_context exists and is valid (llm_conversation_context_id not nil/dangling).
- Confirm the LLM conversation backend service is reachable and credentials/config are correct in the environment.
- Retry the sync once the upstream issue is resolved, or re-trigger indexing via trigger_indexing if status remains stale.
Example fix
// before: error swallowed, status silently stale
rescue LlmConversation::Errors::ConversationError => e
Rails.logger.warn("Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}")
end
// after: track failure in return value so callers can react
rescue LlmConversation::Errors::ConversationError => e
Rails.logger.warn("Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}")
{ status: :sync_failed, failed_file_names: [] }
end Defensive patterns
Strategy: try-catch
Validate before calling
// before calling sync_index_status
if ai_experience.llm_conversation_context_id.blank?
Rails.logger.warn("Skipping index status sync: no llm_conversation_context for ai_experience #{ai_experience.id}")
else
service.sync_index_status(ai_experience)
end Type guard
def syncable?(ai_experience) ai_experience.respond_to?(:llm_conversation_context_id) && ai_experience.llm_conversation_context_id.present? end
Try / catch
begin
result = service.sync_index_status(ai_experience)
rescue LlmConversation::Errors::ConversationError => e
Rails.logger.warn("sync_index_status failed for #{ai_experience.id}: #{e.message}")
# schedule retry / mark status stale
end Prevention
- Validate llm_conversation_context presence before scheduling sync jobs.
- Monitor warn logs for ConversationError frequency to detect upstream LLM service degradation.
- Add a retry/backoff job for failed syncs so status does not stay stale.
- Health-check the LLM conversation backend before bulk syncs.
When it happens
Trigger: Calling sync_index_status(ai_experience) when the associated llm_conversation_context or its remote LLM conversation service raises LlmConversation::Errors::ConversationError — e.g. context missing/invalid, remote API rejected the request, or conversation state is inconsistent while computing new_status/failed_file_names.
Common situations: Background/inline sync of AI Experience context document indexing status; LLM conversation backend downtime or 4xx/5xx responses; an ai_experience whose llm_conversation_context_id points to a deleted or unreachable context; transient network failures during status polling.
Related errors
- AI response appears truncated - the response may have…
- Cannot find criterion with id #
- Cannot regenerate criteria with learning outcomes attached
- Cedar unavailable
- Child content tag was not found for #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/b2f43ca1f79e66d9.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/ai_experiences/conversation_context_documents_service.rb:59
else
"in_progress"
end
ai_experience.update_column(:context_index_status, new_status)
failed_file_names = if new_status == "failed"
failed_doc_ids = documents.select { |d| d["status"] == "failed" }.pluck("id")
ai_experience.ai_experience_context_files
.where(llm_conversation_service_document_id: failed_doc_ids)
.preload(:attachment)
.filter_map { |cf| cf.attachment&.display_name }
else
[]
end
{ status: new_status, failed_file_names: }
rescue LlmConversation::Errors::ConversationError => e
Rails.logger.warn("Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}")
end
def trigger_indexing(ai_experience:, context_file_ids: nil)
context_id = ai_experience.llm_conversation_context_id
return unless context_id.present?
scope = ai_experience.ai_experience_context_files
scope = scope.where(id: context_file_ids) if context_file_ids.present?
context_file_records = scope.preload(:attachment)
return if context_file_records.empty?
context_file_records.each do |context_file|
file = context_file.attachment
next if file.nil? || file.file_state == "deleted"
response = @client.post(
"/contexts/#{context_id}/documents",
payload: { url: file.public_url, sourceType: "file" }View on GitHub (pinned to 1c9f0bb801)