can1357/oh-my-pi · error · ValueError

{field} must be an object

Error message

{field} must be an object

What it means

`_clone_json_object` is the gatekeeper for every field the protocol expects to be a JSON object. It raises this ValueError when the supplied value is not a `dict` (e.g. it is a list, string, or number), before any deep cloning happens. This keeps malformed RPC payloads from being accepted into typed protocol structures.

Source

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

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(
    values: object, *, field: str
) -> tuple[JsonObject, ...] | None:
    if values is None:
        return None
    if not isinstance(values, list):
        raise ValueError(f"{field} must be a list")
    return tuple(_clone_json_object(item, field=f"{field}[]") for item in values)

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the value's type at the call site and ensure it is a dict (`isinstance(value, dict)`); `json.loads` any JSON-string payloads before passing them.
  2. If the value can be absent, use the `_optional_json_object`-backed API or pass None explicitly instead of an empty non-dict value.
  3. Verify client/daemon protocol versions match — a schema drift may have changed this field's shape.
  4. Log/repr the raw payload field to confirm its actual shape before parsing.

Example fix

// before
raw = '{"options": {"timeout": 5}}'
parse_extension_ui_request({"method": "select", "options": raw})
// ValueError: options must be an object

// after
import json
parse_extension_ui_request({"method": "select", "options": json.loads(raw)})
Defensive patterns

Strategy: type-guard

Validate before calling

def is_json_object(value) -> bool:
    return isinstance(value, dict) and all(isinstance(k, str) for k in value)

if not is_json_object(payload.get(field)):
    raise TypeError(f"{field} must be a JSON object, got {type(value).__name__}")

Type guard

def is_json_object(value: object) -> bool:
    return isinstance(value, dict)

Try / catch

try:
    msg = parse_agent_messages(raw)
except ValueError as exc:
    if "must be an object" in str(exc):
        logger.warning("malformed payload for %s: %r", field, raw.get(field))
        msg = fallback_message(raw)
    else:
        raise

Prevention

When it happens

Trigger: Passing a non-dict where an object field is expected: e.g. `_optional_json_object`/`parse_agent_messages`/`parse_assistant_message_event` receiving a JSON array or a JSON-encoded string like '{"a":1}' that was never parsed, or a field that is None being routed through the required variant `_clone_json_objects` items.

Common situations: Double-encoding a nested payload as a JSON string and forgetting `json.loads`; the server/API changed a field from object to list (version mismatch between client and daemon); using `payload.get('x')` and passing a list where a dict belongs; typos so a sibling field is passed as the object.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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