can1357/oh-my-pi · error · ValueError

{field} must contain string keys

Error message

{field} must contain string keys

What it means

_clone_json_value() deep-copies a value while asserting it is valid JSON for the RPC protocol. JSON object keys must be strings; if a dict with a non-str key (int, bool, None, tuple) reaches a parse/copy boundary such as parse_tool_descriptor, parse_compaction_result, or parse_notification, ValueError is raised naming the offending field. This guards against silently coercing or dropping keys when crossing the protocol boundary.

Source

Thrown at python/omp-rpc/src/omp_rpc/protocol.py:152

_ASSISTANT_ERROR_REASON_VALUES: Final[frozenset[str]] = frozenset({"aborted", "error"})
_AUTO_COMPACTION_REASON_VALUES: Final[frozenset[str]] = frozenset(
    {"threshold", "overflow", "idle", "incomplete"}
)
_AUTO_COMPACTION_ACTION_VALUES: Final[frozenset[str]] = frozenset(
    {"context-full", "handoff", "shake", "snapcompact"}
)


def _clone_json_value(value: object, *, field: str) -> 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, field=field) for item in value]
    if isinstance(value, dict):
        cloned: JsonObject = {}
        for key, item in value.items():
            if not isinstance(key, str):
                raise ValueError(f"{field} must contain string keys")
            cloned[key] = _clone_json_value(item, field=field)
        return cloned
    raise ValueError(f"{field} must be JSON-serializable")


def _clone_json_object(value: object, *, field: str) -> JsonObject:
    if not isinstance(value, dict):
        raise ValueError(f"{field} must be an object")
    return cast(JsonObject, _clone_json_value(value, field=field))


def _optional_json_object(value: object, *, field: str) -> JsonObject | None:
    if value is None:
        return None
    return _clone_json_object(value, field=field)


def _optional_json_objects(

View on GitHub (pinned to 9690622007)

Solutions

  1. Convert keys before parsing: `{str(k): v for k, v in data.items()}` (recursively for nested dicts)
  2. If coming from YAML, load with a wrapper that stringifies keys or avoid unquoted numeric keys in the YAML file
  3. Locate which 'field' the message names in the error and fix that specific structure at its source

Example fix

// before
payload = yaml.safe_load(cfg)          # {'8080': ..., 1: ...}
notif = parse_notification(payload)
// after
payload = {str(k): v for k, v in yaml.safe_load(cfg).items()}
notif = parse_notification(payload)
Defensive patterns

Strategy: type-guard

Validate before calling

def stringify_keys(value):
    if isinstance(value, dict):
        return {str(k): stringify_keys(v) for k, v in value.items()}
    if isinstance(value, list):
        return [stringify_keys(v) for v in value]
    return value

Type guard

def has_string_keys(value) -> bool:
    if isinstance(value, dict):
        return all(isinstance(k, str) and has_string_keys(v) for k, v in value.items())
    if isinstance(value, list):
        return all(has_string_keys(v) for v in value)
    return True

Try / catch

try:
    notif = parse_notification(message)
except ValueError as e:
    if 'must contain string keys' in str(e):
        notif = parse_notification(stringify_keys(message))
    else:
        raise

Prevention

When it happens

Trigger: Passing a Python dict parsed from non-JSON sources with int keys (e.g. {1: 'a'}) into descriptor/notification structures; constructing payloads with enum or tuple keys; deserializing from formats that allow non-string keys (YAML, msgpack, Python pickle) and feeding the result to the protocol parser.

Common situations: YAML configs parsed with yaml.safe_load (which keeps int keys like `8080:`), data from databases/msgpack with integer keys, or a message built programmatically where a constant was an enum.

Related errors


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