usestrix/strix · warning · TypeError
Each update must be an object with todo_id
Error message
Each update must be an object with todo_id
What it means
Inside the updates list, every element must be a dict; a non-dict element raises TypeError('Each update must be an object with todo_id'). This runs before the todo_id presence check, so ['t1'] or [42] fails here even though the message also mentions todo_id. Only after passing this check is item.get('todo_id')/'id' looked up.
Source
Thrown at strix/tools/todo/tools.py:174
data: Any = raw_updates
if isinstance(raw_updates, str):
stripped = raw_updates.strip()
if not stripped:
return []
try:
data = json.loads(stripped)
except json.JSONDecodeError as e:
raise ValueError("Updates must be valid JSON") from e
if isinstance(data, dict):
data = [data]
if not isinstance(data, list):
raise TypeError("Updates must be a list of update objects")
normalized: list[dict[str, Any]] = []
for item in data:
if not isinstance(item, dict):
raise TypeError("Each update must be an object with todo_id")
todo_id = item.get("todo_id") or item.get("id")
if not todo_id:
raise ValueError("Each update must include 'todo_id'")
normalized.append(
{
"todo_id": str(todo_id).strip(),
"title": item.get("title"),
"description": item.get("description"),
"priority": item.get("priority"),
"status": item.get("status"),
},
)
return normalized
def _normalize_bulk_todos(raw_todos: Any) -> list[dict[str, Any]]:
if raw_todos is None:
return []View on GitHub (pinned to 8551339130)
Solutions
- Wrap every element as an object: [{"todo_id": "t1", ...}, {"todo_id": "t2", ...}].
- If starting from bare ids, map them: ids.map(id => ({"todo_id": id})).
- For LLM tool schemas, require an array of objects and show an example in the description.
Example fix
# before
bulk_update_todos(updates="[\"t1\", \"t2\"]")
# after
bulk_update_todos(updates='[{"todo_id": "t1"}, {"todo_id": "t2"}]') Defensive patterns
Strategy: type-guard
Validate before calling
def all_elements_are_objects(data: list) -> bool:
return all(isinstance(item, dict) for item in data)
if not all_elements_are_objects(updates):
updates = [{"todo_id": item} if isinstance(item, str) else item for item in updates] Type guard
def is_normalized_updates(items: object) -> bool:
return (
isinstance(items, list)
and all(isinstance(i, dict) and (i.get("todo_id") or i.get("id")) for i in items)
) Try / catch
try:
bulk_update_todos(updates=updates)
except TypeError as exc:
if "object with todo_id" in str(exc):
updates = [u if isinstance(u, dict) else {"todo_id": u} for u in updates]
bulk_update_todos(updates=updates)
else:
raise Prevention
- Wrap bare ids into objects before submitting: ids -> [{'todo_id': id}].
- Specify 'array of objects' in LLM tool schemas with a concrete example.
- Reused id lists across systems should be converted to patch lists at the boundary.
When it happens
Trigger: Passing a mixed list like [{...}, "t2"] or a list of id strings ['t1','t2']. Each element must be an object; ids must be wrapped: [{"todo_id": "t2"}].
Common situations: LLM abbreviating the schema to bare ids; callers converting between list-of-ids and list-of-patches; JSON arrays of strings from external tooling.
Related errors
- Updates must be a list of update objects
- Updates must be valid JSON
- approved must be a boolean
- run.json at {path} is not an object
- Invalid priority. Must be one of: {', '.join(VALID_PRIORITIE
AI-assisted analysis of usestrix/strix@8551339130 (2026-08-15).
Data as JSON: /api/errors/fefb65053c47f22d.
Report an issue: GitHub.