instructure/canvas-lms · error · Pv4EmptyResponse
the response is empty or does not contain expected keys
Error message
the response is empty or does not contain expected keys
What it means
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.
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
Example fix
// before
json = JSON.parse(response.body)
// after
json = begin
response.body.empty? ? {} : JSON.parse(response.body)
rescue JSON::ParserError
{}
end
raise Pv4EmptyResponse, "..." unless json.is_a?(Hash) && json.key?("page_views") Defensive patterns
Strategy: fallback
Validate before calling
body = response_body_if_you_have_it
return {} if body.blank? Type guard
def valid_pv4_json?(parsed)
parsed.is_a?(Hash) && parsed.key?("page_views") && parsed["page_views"].is_a?(Array)
end Try / catch
begin views = PageView.for_user(user) rescue Pv4EmptyResponse views = [] # or retry once for transient truncation end
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Error parsing JSON results from Outcomes Service: #
- Error retrieving results from Outcomes Service: #
- resource not found
- Result format is invalid: #
- Token refresh failed
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/c53998d421275ac3.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/page_view/pv4_client.rb:73
case response.code.to_i
when 400
raise Pv4BadRequest, "invalid request"
when 401
raise Pv4Unauthorized, "unauthorized request"
when 404
raise Pv4NotFound, "resource not found"
when 429
raise Pv4TooManyRequests, "rate limit exceeded"
end
json =
begin
response.body.empty? ? {} : JSON.parse(response.body)
rescue JSON::ParserError
{}
end
raise Pv4EmptyResponse, "the response is empty or does not contain expected keys" unless json["page_views"]
json["page_views"].map! do |pv|
pv["session_id"] = pv.delete("sessionid")
vhost = pv.delete("vhost")
http_request = pv.delete("http_request")
pv["url"] = if vhost.present? && http_request.present?
"#{HostUrl.protocol}://#{vhost}#{http_request}"
elsif http_request.present?
"#{HostUrl.protocol}:#{http_request}"
elsif vhost.present?
"#{HostUrl.protocol}://#{vhost}"
end
pv["context_id"] = pv.delete("canvas_context_id")
pv["context_type"] = pv.delete("canvas_context_type")
pv["updated_at"] = pv["created_at"] = pv.delete("timestamp")
pv["user_agent"] = pv.delete("agent").presence
pv["account_id"] = pv.delete("root_account_id")
pv["remote_ip"] = pv.delete("client_ip")View on GitHub (pinned to 1c9f0bb801)