{"record":{"id":"75250328c0dad42b","repo":"can1357/oh-my-pi","slug":"field-role-must-be-toolresult","errorCode":null,"errorMessage":"{field}.role must be 'toolResult'","messagePattern":"(.+?)\\.role must be 'toolResult'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/omp-rpc/src/omp_rpc/protocol.py","lineNumber":307,"sourceCode":"\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\ndef _parse_tool_result_message(payload: JsonObject, *, field: str) -> ToolResultMessage:\n    message = _parse_agent_message(payload, field=field)\n    if message.get(\"role\") != \"toolResult\":\n        raise ValueError(f\"{field}.role must be 'toolResult'\")\n    return cast(ToolResultMessage, message)\n\n\ndef parse_agent_messages(payload: JsonValue | None) -> tuple[AgentMessage, ...]:\n    if payload is None:\n        return ()\n    if not isinstance(payload, list):\n        raise ValueError(\"messages must be a list\")\n\n    messages: list[AgentMessage] = []\n    for index, item in enumerate(payload):\n        messages.append(\n            _parse_agent_message(\n                _clone_json_object(item, field=f\"messages[{index}]\"),\n                field=f\"messages[{index}]\",\n            )\n        )\n    return tuple(messages)","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/omp-rpc/src/omp_rpc/protocol.py#L289-L325","documentation":"The parser distinguishes tool-result messages from other agent messages by their role literal. _parse_tool_result_message raises this error when the payload's role is a valid agent-message role but not 'toolResult'. The check exists so the subsequent cast to ToolResultMessage is sound and callers can rely on the shape.","triggerScenarios":"Calling parse_notification with a notification whose field carries a message with role 'assistant' or 'user' where a toolResult is required; a server emitting tool results under the wrong role; hand-built payloads with a typo like 'tool_result'.","commonSituations":"Custom server integrations mislabeling tool results; protocol migrations that changed role casing; test fixtures copied from assistant messages.","solutions":["Set the message's role to 'toolResult' in the payload","Fix the server-side emitter to send tool results with role 'toolResult'","Use parse_agent_messages for payloads of mixed/unknown roles instead of the tool-result-specific parser","Verify client/server protocol versions agree on the role literal"],"exampleFix":"// before\n{\"role\": \"tool_result\", \"toolCallId\": \"t1\", ...}\n// after\n{\"role\": \"toolResult\", \"toolCallId\": \"t1\", ...}","handlingStrategy":"type-guard","validationCode":"def is_tool_result(m: dict) -> bool:\n    return isinstance(m, dict) and m.get(\"role\") == \"toolResult\"\n\nif not is_tool_result(msg):\n    raise ValueError(\"expected a toolResult message\")","typeGuard":"from typing import TypeGuard\n\ndef is_tool_result(m: object) -> TypeGuard[dict]:\n    return isinstance(m, dict) and m.get(\"role\") == \"toolResult\"","tryCatchPattern":"try:\n    notification = parse_notification(payload)\nexcept ValueError as e:\n    logger.warning(\"malformed toolResult notification: %s\", e)\n    notification = None","preventionTips":["Use the exact literal 'toolResult' (camelCase) in payloads","Route mixed-role payloads through parse_agent_messages","Pin matching protocol versions on both sides"],"tags":["protocol","validation","serialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}