{"record":{"id":"68039f17af823d63","repo":"shareAI-lab/learn-claude-code","slug":"max-20-todos","errorCode":null,"errorMessage":"Max 20 todos","messagePattern":"Max 20 todos","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"agents/s_full.py","lineNumber":139,"sourceCode":"\n# === SECTION: todos (s03) ===\nclass TodoManager:\n    def __init__(self):\n        self.items = []\n\n    def update(self, items: list) -> str:\n        validated, ip = [], 0\n        for i, item in enumerate(items):\n            content = str(item.get(\"content\", \"\")).strip()\n            status = str(item.get(\"status\", \"pending\")).lower()\n            af = str(item.get(\"activeForm\", \"\")).strip()\n            if not content: raise ValueError(f\"Item {i}: content required\")\n            if status not in (\"pending\", \"in_progress\", \"completed\"):\n                raise ValueError(f\"Item {i}: invalid status '{status}'\")\n            if not af: raise ValueError(f\"Item {i}: activeForm required\")\n            if status == \"in_progress\": ip += 1\n            validated.append({\"content\": content, \"status\": status, \"activeForm\": af})\n        if len(validated) > 20: raise ValueError(\"Max 20 todos\")\n        if ip > 1: raise ValueError(\"Only one in_progress allowed\")\n        self.items = validated\n        return self.render()\n\n    def render(self) -> str:\n        if not self.items: return \"No todos.\"\n        lines = []\n        for item in self.items:\n            m = {\"completed\": \"[x]\", \"in_progress\": \"[>]\", \"pending\": \"[ ]\"}.get(item[\"status\"], \"[?]\")\n            suffix = f\" <- {item['activeForm']}\" if item[\"status\"] == \"in_progress\" else \"\"\n            lines.append(f\"{m} {item['content']}{suffix}\")\n        done = sum(1 for t in self.items if t[\"status\"] == \"completed\")\n        lines.append(f\"\\n({done}/{len(self.items)} completed)\")\n        return \"\\n\".join(lines)\n\n    def has_open_items(self) -> bool:\n        return any(item.get(\"status\") != \"completed\" for item in self.items)\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s_full.py#L121-L157","documentation":"Raised by TodoManager.update in s_full.py when the validated list contains more than 20 items. The cap is a hard limit applied after per-item validation, forcing the caller to keep plans compact. The whole update is atomic: the error means self.items still holds the previous list.","triggerScenarios":"Submitting 21+ todo items in one update() call, e.g. an agent decomposing a large task into one item per subtask and exceeding the cap by a single item.","commonSituations":"Over-granular planning (one todo per file instead of per feature); appending new steps to an existing 20-item list instead of replacing it; migrating a long external checklist into the todo tool verbatim.","solutions":["Consolidate related steps into broader items so the list stays at or under 20","Split work into phases and replace the list per phase rather than accumulating items","Check len(items) <= 20 before calling update and trim/merge client-side"],"exampleFix":"// before\nTODOS.update(items)  # items has 23 entries\n// after\nmerged = merge_related_steps(items)  # <= 20 entries\nTODOS.update(merged)","handlingStrategy":"validation","validationCode":"if len(items) > 20:\n    items = merge_related_steps(items)[:20]  # or split into phases\nTODOS.update(items)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set maxItems: 20 in the tool schema","Replace the todo list per phase instead of accumulating","Prefer fewer, broader todos over many granular ones"],"tags":["todos","validation","limits"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}