{"record":{"id":"5245517768e3e65c","repo":"instructure/canvas-lms","slug":"error-retrieving-results-from-outcomes-service-response-body","errorCode":null,"errorMessage":"Error retrieving results from Outcomes Service: #{response.body}","messagePattern":"Error retrieving results from Outcomes Service: #(.+?)","errorType":"exception","errorClass":"OSFetchError","httpStatus":null,"severity":"error","filePath":"app/helpers/canvas_outcomes_helper.rb","lineNumber":135,"sourceCode":"          results.each do |result|\n            next if result[:attempts].nil?\n\n            result[:attempts].each do |attempt|\n              # Initially metadata was a string, now it's a jsonb data type. When it was a string, canvas needed\n              # to parse the result returned from outcome service\n              next unless attempt[:metadata].is_a? String\n\n              attempt[:metadata] = JSON.parse(attempt[:metadata]) unless attempt[:metadata].nil?\n              attempt[:metadata] = attempt[:metadata].deep_symbolize_keys unless attempt[:metadata].nil?\n            end\n          end\n        end\n        { results:, total_pages: }\n      rescue\n        raise OSFetchError, \"Error parsing JSON results from Outcomes Service: #{response.body}\"\n      end\n    else\n      raise OSFetchError, \"Error retrieving results from Outcomes Service: #{response.body}\"\n    end\n  end\n\n  def outcome_has_alignments?(outcome, context)\n    response = get_outcome_alignments(context, outcome.id, { includes: \"alignments\" })\n    return false if response.nil?\n\n    response.first[:alignments].count > 0\n  end\n\n  def outcome_has_authoritative_results?(outcome, context)\n    assignments = Assignment.active.where(context:).quiz_lti\n\n    return false if assignments.blank?\n\n    assignment_ids = assignments.pluck(:id).join(\",\")\n    assignment_type = \"canvas.assignment.quizzes\"\n    params = {","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/canvas_outcomes_helper.rb#L117-L153","documentation":"get_request_page checks that the Outcomes Service responded with a 2xx status code (/^2/ match on response.code). Any non-2xx (4xx auth failure, 404, 5xx server error) is raised as OSFetchError containing the response body, since authoritative outcome results could not be retrieved.","triggerScenarios":"Outcomes Service returns 401/403 (bad or expired JWT), 404 (wrong URL or context id), or 500 (server-side error) during a get_request/get_request_page call.","commonSituations":"Expired or mis-signed services JWT; wrong outcomes service URL per-environment; the context (course/account) not existing in the outcomes service; outcomes service internal error or maintenance.","solutions":["Read the response body in the error — it usually contains the service's error reason","Verify JWT generation/signing and that shared secrets match between Canvas and the outcomes service","Confirm the outcomes service URL configuration for the environment","Check outcomes service logs for the corresponding 4xx/5xx and fix server-side or correct the request params"],"exampleFix":"// before\nelse\n  raise OSFetchError, \"Error retrieving results from Outcomes Service: #{response.body}\"\n// after\nelse\n  Canvas::Errors.capture(OSFetchError.new(\"status=#{response.code} body=#{response.body[0, 500]}\"))\n  raise OSFetchError, \"Error retrieving results from Outcomes Service (status #{response.code}): #{response.body}\"","handlingStrategy":"try-catch","validationCode":"raise OSFetchError, 'non-2xx' unless response.code.to_s.start_with?('2')","typeGuard":null,"tryCatchPattern":"begin\n  page = get_request_page(context, params)\nrescue OSFetchError => e\n  Canvas::Errors.capture(e)\n  render json: { error: 'outcomes service unavailable' }, status: :bad_gateway\nend","preventionTips":["Verify JWT signing/secrets on every deploy","Environment-check the outcomes service URL","Handle 401/403 distinctly from 5xx in callers"],"tags":["http","api","outcomes-service","http-status"],"backgroundTag":"http-error-response","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"}