can1357/oh-my-pi · error · ValueError

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.

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.

Appendix: 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)