{"record":{"id":"965c1bf25b1c7913","repo":"usestrix/strix","slug":"each-todo-entry-must-be-a-string-or-object-with-a","errorCode":null,"errorMessage":"Each todo entry must be a string or object with a title","messagePattern":"Each todo entry must be a string or object with a title","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"strix/tools/todo/tools.py","lineNumber":217,"sourceCode":"            data = json.loads(stripped)\n        except json.JSONDecodeError:\n            entries = [line.strip(\" -*\\t\") for line in stripped.splitlines() if line.strip(\" -*\\t\")]\n            return [{\"title\": entry} for entry in entries]\n\n    if isinstance(data, dict):\n        data = [data]\n    if not isinstance(data, list):\n        raise TypeError(\"Todos must be provided as a list, dict, or JSON string\")\n\n    normalized: list[dict[str, Any]] = []\n    for item in data:\n        if isinstance(item, str):\n            title = item.strip()\n            if title:\n                normalized.append({\"title\": title})\n            continue\n        if not isinstance(item, dict):\n            raise TypeError(\"Each todo entry must be a string or object with a title\")\n        title = item.get(\"title\", \"\")\n        if not isinstance(title, str) or not title.strip():\n            raise ValueError(\"Each todo entry must include a non-empty 'title'\")\n        normalized.append(\n            {\n                \"title\": title.strip(),\n                \"description\": (item.get(\"description\") or \"\").strip() or None,\n                \"priority\": item.get(\"priority\"),\n            },\n        )\n    return normalized\n\n\ndef _apply_single_update(\n    agent_todos: dict[str, dict[str, Any]],\n    todo_id: str,\n    title: str | None = None,\n    description: str | None = None,","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/tools/todo/tools.py#L199-L235","documentation":"Thrown by the todo normalizer when an element inside the todos list is neither a string nor a dict. Strings are treated as bare titles; dicts carry title/description/priority. Any other element type (number, null, nested list) is rejected with this TypeError.","triggerScenarios":"todos=[42, {'title': 'x'}], todos=[[\"a\"]], or a JSON payload like '[null, \"task\"]' where an element parses to null.","commonSituations":"LLM emitting mixed-type arrays (numbers as task ids), JSON null elements from sparse arrays, or wrapping titles in an extra array layer.","solutions":["Make every list element either a plain title string or an object with a 'title' key","Drop null/undefined entries before calling the tool","Convert numeric entries to strings if they were meant as titles"],"exampleFix":"# before\ntodos = [null, \"write tests\"]\n\n# after\ntodos = [\"write tests\"]","handlingStrategy":"type-guard","validationCode":"entries = [e for e in todos if e is not None]\nif not all(isinstance(e, (str, dict)) for e in entries):\n    raise TypeError(\"todo entries must be str or dict\")","typeGuard":"def is_todo_entry(v) -> bool:\n    return isinstance(v, (str, dict))","tryCatchPattern":"try:\n    tool.write_todos(entries)\nexcept TypeError as e:\n    if \"string or object\" in str(e):\n        entries = [e for e in entries if isinstance(e, (str, dict))]\n        tool.write_todos(entries)","preventionTips":["Filter null and non-str/dict elements out of agent-generated arrays","Validate with a small schema (e.g. pydantic) at the boundary before calling the tool"],"tags":["validation","todo-tool","json"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}