can1357/oh-my-pi · error · ValueError

{field} must be a number

Error message

{field} must be a number

What it means

`_optional_float` accepts absence but otherwise requires a JSON number (int or float), rejecting booleans explicitly and strings/nulls. The value is normalized to float on success. This guards numeric stats like cost or duration.

Source

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

        raise ValueError(f"{field} must be a boolean")
    return value


def _optional_int(payload: JsonObject, field: str) -> int | None:
    value = payload.get(field)
    if value is None:
        return None
    if isinstance(value, bool) or not isinstance(value, int):
        raise ValueError(f"{field} must be an integer")
    return value


def _optional_float(payload: JsonObject, field: str) -> float | None:
    value = payload.get(field)
    if value is None:
        return None
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError(f"{field} must be a number")
    return float(value)


def _tuple_of_strings(values: object, *, field: str) -> tuple[str, ...] | None:
    if values is None:
        return None
    if not isinstance(values, list):
        raise ValueError(f"{field} must be a list")

    result: list[str] = []
    for item in values:
        if not isinstance(item, str):
            raise ValueError(f"{field} must contain only strings")
        result.append(item)
    return tuple(result) or None


def _parse_agent_message(payload: JsonObject, *, field: str) -> AgentMessage:

View on GitHub (pinned to 9690622007)

Solutions

  1. Strip formatting and convert: float(value.replace("$", "")) or float(value) for numeric strings
  2. Fix the producer to emit raw JSON numbers
  3. If precision matters, agree on a string-encoded decimal schema and update the parser, not the payload ad hoc

Example fix

# before
payload = {"total_cost": "1.25"}
state = parse_session_state(payload)  # ValueError: total_cost must be a number
# after
payload = {"total_cost": float("1.25")}
state = parse_session_state(payload)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_optional_number(payload: dict, field: str) -> None:
    value = payload.get(field)
    if value is None:
        return
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise TypeError(f"{field!r} must be a number, got {value!r}")

ensure_optional_number(payload, "total_cost")
parse_session_state(payload)

Type guard

def is_optional_number(payload: dict, field: str) -> bool:
    value = payload.get(field)
    return value is None or (isinstance(value, (int, float)) and not isinstance(value, bool))

Try / catch

try:
    state = parse_session_state(payload)
except ValueError as e:
    logger.error("session stats field not numeric", extra={"payload": payload, "error": str(e)})
    state = None

Prevention

When it happens

Trigger: parse_session_state receives a numeric stat field (e.g. total cost) as a string "$1.25" or "1.25", a bool, or a Decimal-like object that is not int/float.

Common situations: Currency or metric values serialized as formatted strings by the producer; values passed through JSON with custom encoders producing strings; a placeholder bool in a numeric field.

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/18ec476d914367c3. Report an issue: GitHub.