commaai/openpilot · warning · ValueError
message must be a JSON object
Error message
message must be a JSON object
What it means
ValueError from openpilot/system/athena/rpc.py loads(): the payload is valid JSON but its root is not an object (dict). The JSON-RPC layer requires object-rooted messages; arrays, strings, numbers, booleans, or null roots are rejected before is_call/is_response dispatch.
Source
Thrown at openpilot/system/athena/rpc.py:55
def dumps_result(request_id: Any, result: Any) -> str:
return json.dumps({"jsonrpc": JSONRPC_VERSION, "id": request_id, "result": result})
def dumps_error(request_id: Any, message: str, code: int = SERVER_ERROR) -> str:
return json.dumps({
"jsonrpc": JSONRPC_VERSION,
"id": request_id,
"error": {"code": code, "message": message},
})
def loads(raw: str | bytes) -> JsonDict:
if isinstance(raw, bytes):
raw = raw.decode()
data = json.loads(raw)
if not isinstance(data, dict):
raise ValueError("message must be a JSON object")
return data
def is_call(msg: JsonDict) -> bool:
return "method" in msg
def is_response(msg: JsonDict) -> bool:
return "id" in msg and ("result" in msg or "error" in msg)
def error_message(err: Any) -> str:
"""Normalize JSON-RPC object errors and plain-string errors."""
if isinstance(err, str):
return err
if isinstance(err, dict):
data = err.get("data")
if isinstance(data, dict) and data.get("message"):View on GitHub (pinned to 516ec1e682)
Solutions
- Wrap the payload in a JSON-RPC 2.0 object envelope before sending
- If integrating rpc.py directly, validate with isinstance(json.loads(raw), dict) before calling loads()
- Batch requests are not supported: send one object per frame
Example fix
// before ws.send(json.dumps([req1, req2])) // after for req in (req1, req2): ws.send(json.dumps(req))
Defensive patterns
Strategy: type-guard
Validate before calling
import json
def is_json_object(raw) -> bool:
try:
return isinstance(json.loads(raw), dict)
except (ValueError, TypeError):
return False Type guard
import json
from typing import Any
def is_jsonrpc_envelope(data: Any) -> bool:
return isinstance(data, dict) Try / catch
try:
msg = loads(raw)
except ValueError as e:
if 'must be a JSON object' in str(e):
reply_parse_error(raw) # notify sender of malformed envelope
raise Prevention
- Never send JSON arrays or scalars as websocket message roots
- Encode requests with a shared helper that always emits an object envelope
When it happens
Trigger: loads() receiving '[1,2,3]', '"hello"', '42', or 'null'. In athenad this is caught upstream and answered with a parse error; if you call loads()/handle() directly it raises.
Common situations: Clients sending a bare JSON array of batch requests (unsupported), a proxy or test harness double-encoding payloads (string root), or hand-typed websocket frames.
Related errors
- not a valid request or response
- -32000
- -32602
- Profile nickname must be 64 bytes or less
- Confirmation code required but not provided
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/980e7efc04bb84e9.
Report an issue: GitHub.