{"record":{"id":"a6efd994643ad89c","repo":"shareAI-lab/learn-claude-code","slug":"max-20-todos-allowed-a6efd9","errorCode":null,"errorMessage":"Max 20 todos allowed","messagePattern":"Max 20 todos allowed","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"s05_todo_write/code.py","lineNumber":127,"sourceCode":"\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}'\")\n            if status == \"in_progress\":\n                in_progress_count += 1\n            validated.append({\"content\": content, \"status\": status})\n\n        if in_progress_count > 1:","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s05_todo_write/code.py#L109-L145","documentation":"Raised by TodoManager.update in s05_todo_write/code.py when the incoming todos list has more than 20 entries. The cap is checked after parsing/type validation but before per-item validation, so a 25-item list fails immediately. State is untouched: self.items keeps the previous value.","triggerScenarios":"update() called with 21 or more todo dicts (or a JSON string deserializing to such a list).","commonSituations":"Fine-grained agent plans (one todo per file edited); importing long external checklists; appending instead of replacing when the list is already near the cap.","solutions":["Merge related steps to bring the count to 20 or fewer","Replace the list each phase instead of growing it monotonically","Enforce len(todos) <= 20 in the calling code or tool schema (maxItems: 20)"],"exampleFix":"// before\nTODOS.update(all_steps)  # 24 items\n// after\nTODOS.update(all_steps[:20])  # or merge related steps first","handlingStrategy":"validation","validationCode":"MAX = 20\nif len(todos) > MAX:\n    todos = todos[:MAX]  # or merge/drop consciously\nassert len(todos) <= MAX\nTODOS.update(todos)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Declare maxItems: 20 in the tool schema","Replace, don't accumulate, todo lists across phases","Keep plans coarse-grained by design"],"tags":["todos","validation","limits"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}