{"record":{"id":"052ff9f341a9cf37","repo":"shareAI-lab/learn-claude-code","slug":"todos-must-be-a-list-or-json-array-string","errorCode":null,"errorMessage":"todos must be a list or JSON array string","messagePattern":"todos must be a list or JSON array string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s05_todo_write/code.py","lineNumber":122,"sourceCode":"    except Exception as e:\n        return f\"Error: {e}\"\n\n\n# -- New in s05: structured state the model updates --\n\nclass TodoManager:\n    def __init__(self):\n        self.items: list[dict] = []\n\n    def update(self, todos: list | str) -> str:\n        if isinstance(todos, str):\n            try:\n                todos = json.loads(todos)\n            except json.JSONDecodeError:\n                try:\n                    todos = ast.literal_eval(todos)\n                except (SyntaxError, ValueError) as e:\n                    raise ValueError(\"todos must be a list or JSON array string\") from e\n\n        if not isinstance(todos, list):\n            raise ValueError(\"todos must be a list\")\n        if len(todos) > 20:\n            raise ValueError(\"Max 20 todos allowed\")\n\n        validated = []\n        in_progress_count = 0\n        for index, todo in enumerate(todos):\n            if not isinstance(todo, dict):\n                raise ValueError(f\"todos[{index}] must be an object\")\n\n            content = str(todo.get(\"content\", \"\")).strip()\n            status = str(todo.get(\"status\", \"pending\")).lower()\n            if not content:\n                raise ValueError(f\"todos[{index}] requires content\")\n            if status not in (\"pending\", \"in_progress\", \"completed\"):\n                raise ValueError(f\"todos[{index}] has invalid status '{status}'\")","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s05_todo_write/code.py#L104-L140","documentation":"Raised by TodoManager.update in s05_todo_write/code.py when the todos argument is a string that is neither parseable JSON nor parseable by ast.literal_eval. The module accepts both formats because LLM tool callers frequently emit Python-literal lists with single quotes; when both parsers fail, this wrapper error is raised with the original parse error chained as __cause__.","triggerScenarios":"Calling update with '[{\"content\": \"x\",]' (trailing comma, invalid JSON and invalid Python), a string containing markdown fences like \"```json [...]\"\", truncated JSON cut off by an output token limit, or a bare word like \"none\".","commonSituations":"LLM tool calls wrapping the array in prose or code fences; single quotes handled fine by literal_eval but mixed quoting (\"content\": 'x') failing both parsers; response truncation producing half a JSON array.","solutions":["Pass the list as a native JSON array (the tool schema's array type) instead of a string","Strip markdown fences and surrounding prose before calling: s.strip().strip('`').removeprefix('json')","Inspect e.__cause__ to see the exact parse location and fix that character","Validate client-side with json.loads first and retry generation on failure"],"exampleFix":"// before\nTODOS.update(\"```json\\n[{\\\"content\\\": \\\"x\\\", \\\"status\\\": \\\"pending\\\"}]\\n```\")\n// after\nTODOS.update([{\"content\": \"x\", \"status\": \"pending\"}])","handlingStrategy":"validation","validationCode":"import json\n\ndef parse_todos(raw):\n    if isinstance(raw, str):\n        s = raw.strip()\n        if s.startswith('```'):\n            s = s.strip('`').removeprefix('json').strip()\n        raw = json.loads(s)  # raises early with a clear JSON error\n    assert isinstance(raw, list)\n    return raw","typeGuard":null,"tryCatchPattern":"try:\n    TODOS.update(raw)\nexcept ValueError as e:\n    if 'must be a list or JSON array' in str(e):\n        raw = json.loads(strip_fences(raw))\n        TODOS.update(raw)\n    else:\n        raise","preventionTips":["Prefer passing a native array through the tool schema over a string","Strip markdown fences from LLM output before parsing","Inspect __cause__ for the exact syntax error position"],"tags":["todos","json-parsing","validation","agent-tools"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}