{"record":{"id":"b2f43ca1f79e66d9","repo":"instructure/canvas-lms","slug":"document-index-status-sync-failed-for-ai-experience-ai","errorCode":null,"errorMessage":"Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}","messagePattern":"Document index status sync failed for ai_experience #(.+?): #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/services/ai_experiences/conversation_context_documents_service.rb","lineNumber":59,"sourceCode":"                   else\n                     \"in_progress\"\n                   end\n\n      ai_experience.update_column(:context_index_status, new_status)\n\n      failed_file_names = if new_status == \"failed\"\n                            failed_doc_ids = documents.select { |d| d[\"status\"] == \"failed\" }.pluck(\"id\")\n                            ai_experience.ai_experience_context_files\n                                         .where(llm_conversation_service_document_id: failed_doc_ids)\n                                         .preload(:attachment)\n                                         .filter_map { |cf| cf.attachment&.display_name }\n                          else\n                            []\n                          end\n\n      { status: new_status, failed_file_names: }\n    rescue LlmConversation::Errors::ConversationError => e\n      Rails.logger.warn(\"Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}\")\n    end\n\n    def trigger_indexing(ai_experience:, context_file_ids: nil)\n      context_id = ai_experience.llm_conversation_context_id\n      return unless context_id.present?\n\n      scope = ai_experience.ai_experience_context_files\n      scope = scope.where(id: context_file_ids) if context_file_ids.present?\n      context_file_records = scope.preload(:attachment)\n      return if context_file_records.empty?\n\n      context_file_records.each do |context_file|\n        file = context_file.attachment\n        next if file.nil? || file.file_state == \"deleted\"\n\n        response = @client.post(\n          \"/contexts/#{context_id}/documents\",\n          payload: { url: file.public_url, sourceType: \"file\" }","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/ai_experiences/conversation_context_documents_service.rb#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: error swallowed, status silently stale\nrescue LlmConversation::Errors::ConversationError => e\n  Rails.logger.warn(\"Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}\")\nend\n\n// after: track failure in return value so callers can react\nrescue LlmConversation::Errors::ConversationError => e\n  Rails.logger.warn(\"Document index status sync failed for ai_experience #{ai_experience.id}: #{e.message}\")\n  { status: :sync_failed, failed_file_names: [] }\nend","handlingStrategy":"try-catch","validationCode":"// before calling sync_index_status\nif ai_experience.llm_conversation_context_id.blank?\n  Rails.logger.warn(\"Skipping index status sync: no llm_conversation_context for ai_experience #{ai_experience.id}\")\nelse\n  service.sync_index_status(ai_experience)\nend","typeGuard":"def syncable?(ai_experience)\n  ai_experience.respond_to?(:llm_conversation_context_id) && ai_experience.llm_conversation_context_id.present?\nend","tryCatchPattern":"begin\n  result = service.sync_index_status(ai_experience)\nrescue LlmConversation::Errors::ConversationError => e\n  Rails.logger.warn(\"sync_index_status failed for #{ai_experience.id}: #{e.message}\")\n  # schedule retry / mark status stale\nend","preventionTips":["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."],"tags":["llm","sync","rescued-exception","logging"],"backgroundTag":"upstream-api-error","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}