instructure/canvas-lms · error · OSFetchError
Failed to fetch results for context #
Error message
Failed to fetch results for context #{context.id} #{params} What it means
get_request_page in CanvasOutcomesHelper makes an HTTP request to the external Outcomes Service and retries up to MAX_RETRIES. If every attempt raises (network error, DNS failure, TLS error, etc.), it wraps the failure in OSFetchError with the context id and params so the caller knows the authoritative outcome results could not be fetched.
Solutions
- Check Outcomes Service availability/health from the Canvas host (curl the configured URL)
- Verify the outcomes service URL and network egress config for the environment
- Inspect logs for the underlying exception raised on each retry to distinguish timeout vs connection-refused vs DNS
- If transient, re-run the request; consider increasing MAX_RETRIES or adding backoff
Example fix
// before
# all retries fail, no visibility into why
rescue
retry_count += 1
retry if retry_count < MAX_RETRIES
raise OSFetchError, "Failed to fetch results for context #{context.id} #{params}"
// after
rescue => e
retry_count += 1
retry if retry_count < MAX_RETRIES
Canvas::Errors.capture(e)
raise OSFetchError, "Failed to fetch results for context #{context.id} #{params}: #{e.class} #{e.message}" Defensive patterns
Strategy: retry
Validate before calling
# pre-check reachability
uri = URI(outcomes_service_url)
Net::HTTP.start(uri.host, uri.port, open_timeout: 2) { |h| h.head('/') } Try / catch
begin results = get_request(context, params) rescue OSFetchError => e Canvas::Errors.capture(e) fallback_results(context) end
Prevention
- Monitor outcomes service health/endpoints
- Set sensible MAX_RETRIES with exponential backoff
- Alert on OSFetchError rates per environment
When it happens
Trigger: The Outcomes Service host is unreachable or times out on all MAX_RETRIES attempts during get_request/get_request_page; connection refused or reset between Canvas and the outcomes service.
Common situations: Outcomes Service outage or deployment; wrong service URL in config; firewall/security-group blocking egress from the Canvas app container; DNS misconfiguration in an environment.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- Content Export for Outcomes Service failed
- could not retrieve configuration, the server response timed…
- Couldn't send LTI AGS grade progress metric
- # : #
- Error parsing JSON results from Outcomes Service: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/7f93dbda41cbaa7c.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/canvas_outcomes_helper.rb:105
def get_request_page(context, domain, endpoint, jwt, params, page_num, per_page = DEFAULT_PER_PAGE, response_parser_callback = nil)
retry_count = 0
pagination_params = {
per_page:,
page: page_num
}
params = params.merge(pagination_params)
begin
response = CanvasHttp.get(
build_request_url(protocol, domain, endpoint, params),
{
"Authorization" => jwt
}
)
rescue
retry_count += 1
retry if retry_count < MAX_RETRIES
raise OSFetchError, "Failed to fetch results for context #{context.id} #{params}"
end
if /^2/.match?(response.code.to_s)
per_page = response.header["Per-Page"].to_i
total_pages = (response.header["Total"].to_f / per_page).ceil
begin
# If a response_parser_callback is provided, use it to parse the response
if response_parser_callback
results = response_parser_callback.call(response)
else
results = JSON.parse(response.body).deep_symbolize_keys[:results]
results.each do |result|
next if result[:attempts].nil?
result[:attempts].each do |attempt|
# Initially metadata was a string, now it's a jsonb data type. When it was a string, canvas needed
# to parse the result returned from outcome service
next unless attempt[:metadata].is_a? StringView on GitHub (pinned to 1c9f0bb801)