can1357/oh-my-pi · error · RpcError

RPC payload must be JSON-serializable

Error message

RPC payload must be JSON-serializable

What it means

_clone_json_value raises this when it encounters a value that is not JSON-serializable (dict, list, str, int, float, bool, None) while deep-copying an RPC payload. It enforces strict JSON semantics before sending so the wire format is never invalid.

Source

Thrown at python/omp-rpc/src/omp_rpc/client.py:294

    try:
        process.wait(timeout=1.0)
    except subprocess.TimeoutExpired:
        pass


def _clone_json_value(value: object) -> JsonValue:
    if value is None or isinstance(value, (str, int, float, bool)):
        return cast(JsonValue, value)
    if isinstance(value, list):
        return [_clone_json_value(item) for item in value]
    if isinstance(value, dict):
        cloned: JsonObject = {}
        for key, item in value.items():
            if not isinstance(key, str):
                raise RpcError("RPC payload objects must use string keys")
            cloned[key] = _clone_json_value(item)
        return cloned
    raise RpcError("RPC payload must be JSON-serializable")


def _clone_json_object(value: object) -> JsonObject:
    if not isinstance(value, dict):
        raise RpcError("RPC response payload must be an object")
    return cast(JsonObject, _clone_json_value(value))


class RpcError(RuntimeError):
    """Base exception for the Python RPC client."""


class RpcTimeoutError(RpcError):
    """Raised when the server does not respond before a timeout."""


class RpcProcessExitError(RpcError):
    """Raised when the RPC process exits while a request is pending."""

View on GitHub (pinned to 9690622007)

Solutions

  1. Convert non-JSON values before sending: isoformat() for datetimes, sorted(list) for sets, str(path) for Paths.
  2. Use json.loads(json.dumps(payload, default=str)) as a normalization step, or add a default serializer.
  3. Pre-validate the payload with json.dumps(payload) in a try/except before making the call to localize the offending field.
  4. Add explicit serialization helpers at the boundary where payload data is constructed.

Example fix

// before
params = {"due": datetime.now(), "tags": {"a", "b"}}
await client.prompt(params)

// after
params = {"due": datetime.now().isoformat(), "tags": sorted({"a", "b"})}
await client.prompt(params)
Defensive patterns

Strategy: validation

Validate before calling

def assert_json_serializable(payload):
    json.dumps(payload)  # raises TypeError for datetime/set/bytes/etc.

Type guard

def is_json_value(v) -> bool:
    if v is None or isinstance(v, (str, bool, int, float)):
        return True
    if isinstance(v, dict):
        return all(isinstance(k, str) and is_json_value(x) for k, x in v.items())
    if isinstance(v, list):
        return all(is_json_value(x) for x in v)
    return False

Try / catch

try:
    await client.prompt(params)
except RpcError as exc:
    if "JSON-serializable" in str(exc):
        params = json.loads(json.dumps(params, default=str))
        await client.prompt(params)
    else:
        raise

Prevention

When it happens

Trigger: Passing objects like datetime, set, bytes, custom class instances, pathlib.Path, or Decimal inside request params to any client method that sends a payload.

Common situations: Including datetime.now() timestamps, sets of tags, or ORM model objects directly in params; passing Path objects from file-handling code into RPC arguments.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/57442155af12940e. Report an issue: GitHub.