can1357/oh-my-pi · error · ValueError

.role must be 'toolResult

Error message

{field}.role must be 'toolResult'

What it means

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.

Solutions

  1. Set the message's role to 'toolResult' in the payload
  2. Fix the server-side emitter to send tool results with role 'toolResult'
  3. Use parse_agent_messages for payloads of mixed/unknown roles instead of the tool-result-specific parser
  4. Verify client/server protocol versions agree on the role literal

Example fix

// before
{"role": "tool_result", "toolCallId": "t1", ...}
// after
{"role": "toolResult", "toolCallId": "t1", ...}
Defensive patterns

Strategy: type-guard

Validate before calling

def is_tool_result(m: dict) -> bool:
    return isinstance(m, dict) and m.get("role") == "toolResult"

if not is_tool_result(msg):
    raise ValueError("expected a toolResult message")

Type guard

from typing import TypeGuard

def is_tool_result(m: object) -> TypeGuard[dict]:
    return isinstance(m, dict) and m.get("role") == "toolResult"

Try / catch

try:
    notification = parse_notification(payload)
except ValueError as e:
    logger.warning("malformed toolResult notification: %s", e)
    notification = None

Prevention

When it happens

Trigger: 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'.

Common situations: Custom server integrations mislabeling tool results; protocol migrations that changed role casing; test fixtures copied from assistant messages.

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/75250328c0dad42b. Report an issue: GitHub.

Appendix: source

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

def _parse_agent_message(payload: JsonObject, *, field: str) -> AgentMessage:
    _require_literal(
        payload.get("role"), _AGENT_MESSAGE_ROLE_VALUES, field=f"{field}.role"
    )
    return cast(AgentMessage, _clone_json_object(payload, field=field))


def _parse_assistant_message(payload: JsonObject, *, field: str) -> AssistantMessage:
    message = _parse_agent_message(payload, field=field)
    if message.get("role") != "assistant":
        raise ValueError(f"{field}.role must be 'assistant'")
    return cast(AssistantMessage, message)


def _parse_tool_result_message(payload: JsonObject, *, field: str) -> ToolResultMessage:
    message = _parse_agent_message(payload, field=field)
    if message.get("role") != "toolResult":
        raise ValueError(f"{field}.role must be 'toolResult'")
    return cast(ToolResultMessage, message)


def parse_agent_messages(payload: JsonValue | None) -> tuple[AgentMessage, ...]:
    if payload is None:
        return ()
    if not isinstance(payload, list):
        raise ValueError("messages must be a list")

    messages: list[AgentMessage] = []
    for index, item in enumerate(payload):
        messages.append(
            _parse_agent_message(
                _clone_json_object(item, field=f"messages[{index}]"),
                field=f"messages[{index}]",
            )
        )
    return tuple(messages)

View on GitHub (pinned to 9690622007)