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

  1. Wrap the payload in a JSON-RPC 2.0 object envelope before sending
  2. If integrating rpc.py directly, validate with isinstance(json.loads(raw), dict) before calling loads()
  3. 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

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


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/980e7efc04bb84e9. Report an issue: GitHub.