{"record":{"id":"c53998d421275ac3","repo":"instructure/canvas-lms","slug":"the-response-is-empty-or-does-not-contain-expected-keys","errorCode":null,"errorMessage":"the response is empty or does not contain expected keys","messagePattern":"the response is empty or does not contain expected keys","errorType":"exception","errorClass":"Pv4EmptyResponse","httpStatus":null,"severity":"error","filePath":"app/models/page_view/pv4_client.rb","lineNumber":73,"sourceCode":"\n      case response.code.to_i\n      when 400\n        raise Pv4BadRequest, \"invalid request\"\n      when 401\n        raise Pv4Unauthorized, \"unauthorized request\"\n      when 404\n        raise Pv4NotFound, \"resource not found\"\n      when 429\n        raise Pv4TooManyRequests, \"rate limit exceeded\"\n      end\n\n      json =\n        begin\n          response.body.empty? ? {} : JSON.parse(response.body)\n        rescue JSON::ParserError\n          {}\n        end\n      raise Pv4EmptyResponse, \"the response is empty or does not contain expected keys\" unless json[\"page_views\"]\n\n      json[\"page_views\"].map! do |pv|\n        pv[\"session_id\"] = pv.delete(\"sessionid\")\n        vhost = pv.delete(\"vhost\")\n        http_request = pv.delete(\"http_request\")\n        pv[\"url\"] = if vhost.present? && http_request.present?\n                      \"#{HostUrl.protocol}://#{vhost}#{http_request}\"\n                    elsif http_request.present?\n                      \"#{HostUrl.protocol}:#{http_request}\"\n                    elsif vhost.present?\n                      \"#{HostUrl.protocol}://#{vhost}\"\n                    end\n        pv[\"context_id\"] = pv.delete(\"canvas_context_id\")\n        pv[\"context_type\"] = pv.delete(\"canvas_context_type\")\n        pv[\"updated_at\"] = pv[\"created_at\"] = pv.delete(\"timestamp\")\n        pv[\"user_agent\"] = pv.delete(\"agent\").presence\n        pv[\"account_id\"] = pv.delete(\"root_account_id\")\n        pv[\"remote_ip\"] = pv.delete(\"client_ip\")","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/page_view/pv4_client.rb#L55-L91","documentation":"Pv4Client#fetch raises Pv4EmptyResponse when the PV4 service response body is empty, unparseable JSON, or parses to a JSON object without a \"page_views\" key. It guards against unexpected/blank payloads so callers never process malformed data.","triggerScenarios":"PV4 service returns 200 with an empty body, returns non-JSON content, or returns valid JSON lacking the \"page_views\" key — e.g. a proxy stripped the body, a load balancer error page, or a PV4 version change to the response schema.","commonSituations":"PV4 service partially deployed or behind a misconfigured proxy/gateway; PV4 API version changed response shape; transient network issues truncating the body; hitting a health-check endpoint instead of the data endpoint.","solutions":["Inspect the raw PV4 response (curl the endpoint) to see what is actually returned","Verify PV4 service version matches the response schema Canvas expects (\"page_views\" key)","Check proxies/load balancers for body modification or truncation","Treat Pv4EmptyResponse as a transient/empty result and retry or show empty history"],"exampleFix":"// before\njson = JSON.parse(response.body)\n// after\njson = begin\n  response.body.empty? ? {} : JSON.parse(response.body)\nrescue JSON::ParserError\n  {}\nend\nraise Pv4EmptyResponse, \"...\" unless json.is_a?(Hash) && json.key?(\"page_views\")","handlingStrategy":"fallback","validationCode":"body = response_body_if_you_have_it\nreturn {} if body.blank?","typeGuard":"def valid_pv4_json?(parsed)\n  parsed.is_a?(Hash) && parsed.key?(\"page_views\") && parsed[\"page_views\"].is_a?(Array)\nend","tryCatchPattern":"begin\n  views = PageView.for_user(user)\nrescue Pv4EmptyResponse\n  views = [] # or retry once for transient truncation\nend","preventionTips":["Pin and monitor the PV4 service version for schema changes","Check proxy/load-balancer configs that strip or truncate bodies","Alert on Pv4EmptyResponse frequency as a service-health signal","Retry once on transient occurrences before showing empty history"],"tags":["http","json","api","unexpected-response"],"backgroundTag":"unexpected-response-shape","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"}