{"record":{"id":"fcbd8d1ef1e16b1d","repo":"shareAI-lab/learn-claude-code","slug":"todos-index-must-be-an-object","errorCode":null,"errorMessage":"todos[{index}] must be an object","messagePattern":"todos\\[(.+?)\\] must be an object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s05_todo_write/code.py","lineNumber":133,"sourceCode":"        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}'\")\n            if status == \"in_progress\":\n                in_progress_count += 1\n            validated.append({\"content\": content, \"status\": status})\n\n        if in_progress_count > 1:\n            raise ValueError(\"Only one todo can be in_progress at a time\")\n\n        self.items = validated\n        return self.render()\n\n    def render(self) -> str:","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s05_todo_write/code.py#L115-L151","documentation":"Raised by TodoManager.update in s05_todo_write/code.py when an element of the todos list is not a dict. This module does stricter type checking than the s_full variant (which string-coerces via item.get): here non-object elements fail immediately. Common with mixed arrays produced by lenient LLM output, e.g. a bare string alongside proper objects.","triggerScenarios":"update([\"write tests\", {\"content\": \"ship\", \"status\": \"pending\"}]) — element 0 is a str; a JSON string like '[\"step 1\", \"step 2\"]' (list of strings) parsed from a caller that assumed plain-text todos were supported.","commonSituations":"LLMs simplifying the schema by sending plain strings; callers migrating from a checklist API that accepted strings; heterogeneous arrays where one malformed element poisons the batch.","solutions":["Convert string elements to objects: [{\"content\": s, \"status\": \"pending\"} for s in todos]","Validate each element with isinstance(todo, dict) before calling and drop/fix offenders","Use the reported index to locate the malformed element in the submitted list"],"exampleFix":"// before\nTODOS.update([\"write tests\", \"ship\"])\n// after\nTODOS.update([{\"content\": s, \"status\": \"pending\"} for s in [\"write tests\", \"ship\"]])","handlingStrategy":"type-guard","validationCode":"todos = [t if isinstance(t, dict) else {\"content\": str(t), \"status\": \"pending\"} for t in todos]\nassert all(isinstance(t, dict) for t in todos)\nTODOS.update(todos)","typeGuard":"def all_objects(lst: list) -> bool:\n    return all(isinstance(x, dict) for x in lst)","tryCatchPattern":"try:\n    TODOS.update(todos)\nexcept ValueError as e:\n    m = re.search(r\"todos\\[(\\d+)\\] must be an object\", str(e))\n    if m:\n        i = int(m.group(1))\n        todos[i] = {\"content\": str(todos[i]), \"status\": \"pending\"}\n        TODOS.update(todos)\n    else:\n        raise","preventionTips":["Coerce string elements to {content, status} objects before calling","Use a strict schema (type: array, items: type: object) in the tool definition","Reject heterogeneous arrays early in your own layer"],"tags":["todos","type-validation","agent-tools"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}