instructure/canvas-lms · error · RuntimeError

Error retrieving export state for Outcomes Service: #

Error message

Error retrieving export state for Outcomes Service: #{response.body}

What it means

export_completed? raises this when the outcomes service responds with a non-2xx status while polling export state, embedding the response body. It means the state poll itself failed, not that the export failed.

Solutions

  1. Inspect response body in the message (401/404/500 etc.) and fix auth or URL accordingly
  2. Verify the export_id and course are correct for the configured outcomes service
  3. Check/refresh headers_for JWT generation
  4. Retry the poll after service recovery
Defensive patterns

Strategy: retry

Try / catch

begin
  done = MigrationService.export_completed?(export_data)
rescue RuntimeError => e
  raise unless e.message.start_with?('Error retrieving export state')
  attempts += 1
  retry if attempts < 3 && e.message.include?('50')
  Canvas::Errors.capture(e)
end

Prevention

When it happens

Trigger: GET to /api/content_exports/:id returns 4xx/5xx — expired/invalid JWT in headers_for, unknown export_id, or the service being down/erroring.

Common situations: Token expiry during long polls; export id stale or deleted; outcomes service outage or wrong URL configuration.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/02812208c8a6c21b. Report an issue: GitHub.

Appendix: source

Thrown at app/models/outcomes_service/migration_service.rb:72

      def export_completed?(export_data)
        content_export_url = "#{OutcomesService::Service.url(export_data[:course])}/api/content_exports/#{export_data[:export_id]}"
        response = CanvasHttp.get(
          content_export_url,
          headers_for(export_data[:course], "content_migration.export", id: export_data[:export_id])
        )
        if /^2/.match?(response.code.to_s)
          json = JSON.parse(response.body)
          case json["state"]
          when "completed"
            true
          when "failed"
            raise "Content Export for Outcomes Service failed"
          else
            false
          end
        else
          raise "Error retrieving export state for Outcomes Service: #{response.body}"
        end
      end

      def retrieve_export(export_data)
        content_export_url = "#{OutcomesService::Service.url(export_data[:course])}/api/content_exports/#{export_data[:export_id]}"
        response = CanvasHttp.get(
          content_export_url,
          headers_for(export_data[:course], "content_migration.export", id: export_data[:export_id])
        )
        if /^2/.match?(response.code.to_s)
          json = JSON.parse(response.body)
          json["data"]
        else
          raise "Error retrieving export for Outcomes Service: #{response.body}"
        end
      end

      def send_imported_content(course, content_migration, imported_content)

View on GitHub (pinned to 1c9f0bb801)