can1357/oh-my-pi · error · RpcError

RPC payload objects must use string keys

Error message

RPC payload objects must use string keys

What it means

_clone_json_value deep-copies outgoing payload structures and validates them as proper JSON. Python dicts may have non-string keys (int, bool, None, tuple); JSON objects require string keys, so this RpcError is raised during payload cloning before serialization.

Source

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

    except subprocess.TimeoutExpired:
        pass
    _signal_group(signal.SIGKILL)
    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."""

View on GitHub (pinned to 9690622007)

Solutions

  1. Stringify all dict keys before passing payloads to the client: {str(k): v for k, v in d.items()}.
  2. Represent keyed data as a list of {"key": ..., "value": ...} objects instead of a mapping with non-string keys.
  3. Pre-validate payload structure with a recursive JSON-key check in your own code.
  4. If keys come from an upstream source (config file, DB), normalize them at ingestion time.

Example fix

// before
params = {"todos": {1: "write tests"}}
await client.request("todo/update", params)

// after
params = {"todos": {str(k): v for k, v in {1: "write tests"}.items()}}
await client.request("todo/update", params)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_str_keys(value, path="$"):
    if isinstance(value, dict):
        for k, v in value.items():
            if not isinstance(k, str):
                raise ValueError(f"non-string key at {path}: {k!r}")
            ensure_str_keys(v, f"{path}.{k}")
    elif isinstance(value, list):
        for i, v in enumerate(value):
            ensure_str_keys(v, f"{path}[{i}]")

Type guard

def has_str_keys(d: dict) -> bool:
    return all(isinstance(k, str) for k in d)

Try / catch

try:
    await client.request(method, params)
except RpcError as exc:
    if "must use string keys" in str(exc):
        params = json.loads(json.dumps(params, default=str))
        await client.request(method, params)
    else:
        raise

Prevention

When it happens

Trigger: Passing a request payload/params containing a dict with non-string keys, e.g. {1: "a"} or {None: "x"}, to a client request such as prompt/params construction.

Common situations: Building params programmatically with integer IDs as keys, converting from formats that allow non-string keys (YAML, SQLite rows), or naive reuse of in-memory caches keyed by objects as the payload itself.

Related errors


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