can1357/oh-my-pi · error · ValueError
tasks must be a list
Error message
tasks must be a list
What it means
parse_todo_phase() validates the shape of a TodoPhase RPC payload. When the "tasks" key is present but is not a JSON list, the parser refuses to coerce it and raises ValueError, since tasks must be a list of todo-item objects to become the tasks tuple.
Source
Thrown at python/omp-rpc/src/omp_rpc/protocol.py:1363
_require_literal(
payload.get("status", "pending"),
_TODO_STATUS_VALUES,
field="todo.status",
),
),
notes=_optional_str(payload, "notes"),
details=_optional_str(payload, "details"),
blocker=_optional_str(payload, "blocker"),
)
def parse_todo_phase(payload: JsonObject) -> TodoPhase:
raw_tasks = payload.get("tasks")
if raw_tasks is None:
tasks = ()
else:
if not isinstance(raw_tasks, list):
raise ValueError("tasks must be a list")
tasks = tuple(
parse_todo_item(_clone_json_object(item, field="tasks[]"))
for item in raw_tasks
)
return TodoPhase(
id=str(payload.get("id", "")),
name=_require_str(payload, "name"),
tasks=tasks,
)
def parse_todo_phases(payload: JsonValue | None) -> tuple[TodoPhase, ...]:
if not isinstance(payload, list):
return ()
return tuple(parse_todo_phase(cast(JsonObject, item)) for item in payload)
def parse_session_state(payload: JsonObject) -> SessionState:View on GitHub (pinned to 9690622007)
Solutions
- Inspect the raw payload and ensure "tasks" is serialized as a JSON array of todo-item objects.
- Fix the producer (server/tool) to emit tasks as a list; if keyed, convert values to an array.
- Check client/server protocol versions match; upgrade omp-rpc on both sides.
- If interception/middleware is in play, verify it does not transform arrays into objects.
Example fix
// before (server emits object)
{"id": "p1", "tasks": {"t1": {"text": "do x"}}}
// after
{"id": "p1", "tasks": [{"id": "t1", "text": "do x"}]} Defensive patterns
Strategy: type-guard
Validate before calling
raw = payload.get("tasks")
if raw is not None and not isinstance(raw, list):
raise TypeError(f"tasks must be a list, got {type(raw).__name__}") Type guard
def is_task_list(payload: dict) -> bool:
raw = payload.get("tasks")
return raw is None or (isinstance(raw, list) and all(isinstance(t, dict) for t in raw)) Try / catch
try:
phase = parse_todo_phase(payload)
except ValueError as exc:
logger.error("invalid todo phase payload", extra={"payload": payload})
raise ProtocolError("malformed todo phase") from exc Prevention
- Validate RPC payloads against the protocol JSON schema at the client boundary.
- Pin matching client/server protocol versions in CI.
- Add contract tests for todo-phase serialization on the server side.
When it happens
Trigger: Calling an RPC that returns a TodoPhase where the server serializes "tasks" as an object, string, or null-like non-list value instead of an array (e.g. a map keyed by task id, or a dict instead of a list).
Common situations: Version mismatch between client and server protocol schemas; a hand-written mock or test fixture using a dict for tasks; a proxy or middleware re-encoding the array as an object; a server bug in a custom tool emitting todo phases.
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
- cycle_model response did not include a model
- ready.supportedProtocolVersions must be integers
- RPC frame must be a JSON object
- Replacement text is not valid UTF-8: {err}
- invalid glob `{pattern}`: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d207a7c6f7bd93dd.
Report an issue: GitHub.