{"record":{"id":"267c3c64c3d8b92b","repo":"can1357/oh-my-pi","slug":"field-must-contain-only-strings","errorCode":null,"errorMessage":"{field} must contain only strings","messagePattern":"(.+?) must contain only strings","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":285,"sourceCode":"def _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:\n    _require_literal(\n        payload.get(\"role\"), _AGENT_MESSAGE_ROLE_VALUES, field=f\"{field}.role\"\n    )\n    return cast(AgentMessage, _clone_json_object(payload, field=field))\n\n\ndef _parse_assistant_message(payload: JsonObject, *, field: str) -> AssistantMessage:\n    message = _parse_agent_message(payload, field=field)\n    if message.get(\"role\") != \"assistant\":\n        raise ValueError(f\"{field}.role must be 'assistant'\")\n    return cast(AssistantMessage, message)\n\n","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L267-L303","documentation":"After confirming the field is a list, `_tuple_of_strings` checks each element is a string. This error fires when at least one element is a number, bool, null, or nested structure; the parser aborts instead of returning a partially-converted tuple.","triggerScenarios":"parse_model_info or parse_extension_ui_request receives a list field containing mixed types — e.g. [\"text\", 2] or [None] in a field like supported input modes or argument names.","commonSituations":"Numeric enum values sent where string enums are expected; a producer includes null placeholders in lists; mixed-type arrays from dynamically typed producer code.","solutions":["Map elements to strings before parsing: [str(x) for x in value] — verify str() preserves wire semantics (numeric enums may need a lookup table)","Fix the producer so the list contains only JSON strings","Drop None entries if they are placeholders: [x for x in value if x is not None]"],"exampleFix":"# before\npayload = {\"modes\": [\"text\", 2]}\ninfo = parse_model_info(payload)  # ValueError: modes must contain only strings\n# after\npayload = {\"modes\": [str(x) for x in [\"text\", 2]]}\ninfo = parse_model_info(payload)","handlingStrategy":"validation","validationCode":"def ensure_list_of_str(payload: dict, field: str) -> None:\n    value = payload.get(field)\n    if value is None:\n        return\n    if not isinstance(value, list):\n        raise TypeError(f\"{field!r} must be a list\")\n    for item in value:\n        if not isinstance(item, str):\n            raise TypeError(f\"{field!r} contains non-string: {item!r}\")\n\nensure_list_of_str(payload, \"modes\")\nparse_model_info(payload)","typeGuard":"def is_str_tuple(value: object) -> bool:\n    return isinstance(value, list) and all(isinstance(x, str) for x in value)","tryCatchPattern":"try:\n    req = parse_extension_ui_request(payload)\nexcept ValueError as e:\n    logger.error(\"string-list field had mixed types\", extra={\"payload\": payload, \"error\": str(e)})\n    raise ProtocolError(\"malformed extension request\") from e","preventionTips":["Enforce homogeneous string lists at the producer with typed models (list[str] annotations, pydantic)","Filter or map None placeholders out before serialization","Use string enums on the wire; map numeric enums at the producer boundary","Round-trip fixture payloads through the parser in CI to catch drift"],"tags":["python","rpc","type-validation","list-elements"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}