{"record":{"id":"18ec476d914367c3","repo":"can1357/oh-my-pi","slug":"field-must-be-a-number","errorCode":null,"errorMessage":"{field} must be a number","messagePattern":"(.+?) must be a number","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":272,"sourceCode":"        raise ValueError(f\"{field} must be a boolean\")\n    return value\n\n\ndef _optional_int(payload: JsonObject, field: str) -> int | None:\n    value = payload.get(field)\n    if value is None:\n        return None\n    if isinstance(value, bool) or not isinstance(value, int):\n        raise ValueError(f\"{field} must be an integer\")\n    return value\n\n\ndef _optional_float(payload: JsonObject, field: str) -> float | None:\n    value = payload.get(field)\n    if value is None:\n        return None\n    if isinstance(value, bool) or not isinstance(value, (int, float)):\n        raise ValueError(f\"{field} must be a number\")\n    return float(value)\n\n\ndef _tuple_of_strings(values: object, *, field: str) -> tuple[str, ...] | None:\n    if values is None:\n        return None\n    if not isinstance(values, list):\n        raise ValueError(f\"{field} must be a list\")\n\n    result: list[str] = []\n    for item in values:\n        if not isinstance(item, str):\n            raise ValueError(f\"{field} must contain only strings\")\n        result.append(item)\n    return tuple(result) or None\n\n\ndef _parse_agent_message(payload: JsonObject, *, field: str) -> AgentMessage:","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L254-L290","documentation":"`_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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Strip formatting and convert: float(value.replace(\"$\", \"\")) or float(value) for numeric strings","Fix the producer to emit raw JSON numbers","If precision matters, agree on a string-encoded decimal schema and update the parser, not the payload ad hoc"],"exampleFix":"# before\npayload = {\"total_cost\": \"1.25\"}\nstate = parse_session_state(payload)  # ValueError: total_cost must be a number\n# after\npayload = {\"total_cost\": float(\"1.25\")}\nstate = parse_session_state(payload)","handlingStrategy":"validation","validationCode":"def ensure_optional_number(payload: dict, field: str) -> None:\n    value = payload.get(field)\n    if value is None:\n        return\n    if isinstance(value, bool) or not isinstance(value, (int, float)):\n        raise TypeError(f\"{field!r} must be a number, got {value!r}\")\n\nensure_optional_number(payload, \"total_cost\")\nparse_session_state(payload)","typeGuard":"def is_optional_number(payload: dict, field: str) -> bool:\n    value = payload.get(field)\n    return value is None or (isinstance(value, (int, float)) and not isinstance(value, bool))","tryCatchPattern":"try:\n    state = parse_session_state(payload)\nexcept ValueError as e:\n    logger.error(\"session stats field not numeric\", extra={\"payload\": payload, \"error\": str(e)})\n    state = None","preventionTips":["Send raw JSON numbers, not formatted strings (\"$1.25\", \"1,250.00\")","Keep numeric formatting for display only, never on the wire","Exclude bools from numeric fields (bool subclasses int)","Agree on fixed-vs-float representation for money across client and server"],"tags":["python","rpc","type-validation","numeric"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}