can1357/oh-my-pi · error · ValueError
{field}.role must be 'assistant'
Error message
{field}.role must be 'assistant' What it means
The omp-rpc protocol parser validates every agent message's role before casting. This error is raised when a payload passed to _parse_assistant_message (via parse_assistant_message_event, e.g. the 'partial' of an assistantMessageEvent or a 'done' event's message) parses as a valid agent message but its role is not 'assistant'. The library throws it because the typed AssistantMessage contract requires role == 'assistant'; a different role would silently violate the type cast that follows.
Source
Thrown at python/omp-rpc/src/omp_rpc/protocol.py:300
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:
_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):View on GitHub (pinned to 9690622007)
Solutions
- Inspect the notification payload and ensure the partial/message object inside the assistantMessageEvent has role='assistant'
- Fix the server/emitter side so assistant events carry assistant-role messages
- If consuming raw JSON, route messages with other roles through the appropriate parser (parse_agent_messages) instead of the assistant-specific one
- Check for protocol version mismatch between client and server and upgrade the matching omp-rpc package
Example fix
// before
parse_assistant_message_event({"type": "start", "partial": {"role": "user", ...}})
// after
parse_assistant_message_event({"type": "start", "partial": {"role": "assistant", "content": [], ...}}) Defensive patterns
Strategy: type-guard
Validate before calling
def is_assistant_message(m: dict) -> bool:
return isinstance(m, dict) and m.get("role") == "assistant"
partial = event.get("partial")
if not is_assistant_message(partial):
raise ValueError("event partial is not an assistant message") Type guard
from typing import TypeGuard
def is_assistant_message(m: object) -> TypeGuard[dict]:
return isinstance(m, dict) and m.get("role") == "assistant" Try / catch
try:
event = parse_assistant_message_event(payload)
except ValueError as e:
logger.warning("malformed assistant event: %s", e)
event = None Prevention
- Check the role field of event partials before parsing
- Keep client and server protocol versions in sync
- Use parse_agent_messages for payloads of unknown role
When it happens
Trigger: Passing a notification whose assistantMessageEvent.partial (or assistantMessageEvent.message for done events) has role 'user' or 'toolResult' instead of 'assistant'; a server bug serializing the wrong message into an assistant event; mixing up message objects when hand-constructing events.
Common situations: A custom or older server implementation emits the full transcript message rather than the assistant message in event partials; client code plumbs user messages into assistant-message-event handlers; a protocol version change renamed or reshaped roles.
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
- {field}.role must be 'toolResult'
- runInTerminal request did not include a command
- ${label} must be a string
- Unknown daemon state: ${state}
- daemon.readyPending must be an array
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/40a5fe7106919600.
Report an issue: GitHub.