instructure/canvas-lms · error · OSFetchError
Error parsing JSON results from Outcomes Service: #
Error message
Error parsing JSON results from Outcomes Service: #{response.body} What it means
After a 2xx response from the Outcomes Service, get_request_page parses the body as JSON and normalizes result attempts. If JSON.parse (and the subsequent shape handling) raises, it is rescued and re-raised as OSFetchError including the raw response body, because a 200 with unparseable/missing content still yields no usable results.
Solutions
- Log/inspect the response body included in the error to see what actually came back
- Verify the Outcomes Service version matches the response schema Canvas expects
- Check for proxies (LB, auth middleware) returning HTML with 200 status
- Add a schema/validation step before parsing to fail fast with a clearer message
Example fix
// before
rescue
raise OSFetchError, "Error parsing JSON results from Outcomes Service: #{response.body}"
// after
rescue JSON::ParserError => e
raise OSFetchError, "Error parsing JSON results from Outcomes Service (#{e.message}): #{response.body[0, 500]}" Defensive patterns
Strategy: try-catch
Validate before calling
# validate body looks like JSON before parsing raise OSFetchError, 'empty body' if response.body.to_s.strip.empty? JSON.parse(response.body) # raises JSON::ParserError with position info
Try / catch
begin
parsed = JSON.parse(response.body)
rescue JSON::ParserError => e
raise OSFetchError, "Error parsing JSON results from Outcomes Service: #{e.message} #{response.body[0, 200]}"
end Prevention
- Log a truncated body on parse failure
- Pin and test against the outcomes service API version
- Detect proxy/HTML 200 responses before parsing
When it happens
Trigger: Outcomes Service returns a 2xx whose body is not valid JSON (HTML error page from a proxy, empty body, truncated response) or valid JSON with an unexpected shape (missing 'results'/'attempts' keys where deep_symbolize_keys/normalization is applied).
Common situations: Load balancer or auth proxy intercepting and returning an HTML 200 page; outcomes service bug emitting malformed JSON; API version drift changing the response envelope.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- Content Export for Outcomes Service failed
- # : #
- Error queueing export for Outcomes Service: #
- Error retrieving export for Outcomes Service: #
- Error retrieving export state for Outcomes Service: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/8beaad9c271dea0a.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/canvas_outcomes_helper.rb:132
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? String
attempt[:metadata] = JSON.parse(attempt[:metadata]) unless attempt[:metadata].nil?
attempt[:metadata] = attempt[:metadata].deep_symbolize_keys unless attempt[:metadata].nil?
end
end
end
{ results:, total_pages: }
rescue
raise OSFetchError, "Error parsing JSON results from Outcomes Service: #{response.body}"
end
else
raise OSFetchError, "Error retrieving results from Outcomes Service: #{response.body}"
end
end
def outcome_has_alignments?(outcome, context)
response = get_outcome_alignments(context, outcome.id, { includes: "alignments" })
return false if response.nil?
response.first[:alignments].count > 0
end
def outcome_has_authoritative_results?(outcome, context)
assignments = Assignment.active.where(context:).quiz_lti
return false if assignments.blank?
View on GitHub (pinned to 1c9f0bb801)