{"record":{"id":"3e879a10a264954b","repo":"shareAI-lab/learn-claude-code","slug":"todos-index-has-invalid-status-status","errorCode":null,"errorMessage":"todos[{index}] has invalid status '{status}'","messagePattern":"todos\\[(.+?)\\] has invalid status '(.+?)'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s05_todo_write/code.py","lineNumber":140,"sourceCode":"                    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:\n        if not self.items:\n            return \"No todos.\"\n\n        lines = []\n        for todo in self.items:\n            marker = {\n                \"pending\": \"[ ]\",","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s05_todo_write/code.py#L122-L158","documentation":"Raised by TodoManager.update in s05_todo_write/code.py when a todo's status (string-coerced and lowercased) is not pending, in_progress, or completed. The invalid value is echoed in the message. Statuses are normalized with str().lower(), so casing is forgiven but any other wording, separator style, or non-string type fails.","triggerScenarios":"status: \"done\", \"in-progress\", \"inprogress\", \"blocked\", \"todo\", True (coerces to \"true\"), or 1 (coerces to \"1\").","commonSituations":"Agents using natural words like done/blocked; camelCase \"inProgress\" lowercasing to \"inprogress\" which still fails; statuses imported from an external tracker with a richer enum.","solutions":["Map external statuses to the allowed trio before calling (done->completed, doing->in_progress)","Validate status in ('pending','in_progress','completed') client-side and correct early","Remember casing is tolerated but the exact snake_case words are required"],"exampleFix":"// before\n{\"content\": \"ship\", \"status\": \"done\"}\n// after\n{\"content\": \"ship\", \"status\": \"completed\"}","handlingStrategy":"validation","validationCode":"ALLOWED = ('pending', 'in_progress', 'completed')\nMAP = {'done': 'completed', 'todo': 'pending', 'doing': 'in_progress'}\nfor t in todos:\n    s = str(t.get('status', 'pending')).lower()\n    t['status'] = MAP.get(s, s)\nassert all(t['status'] in ALLOWED for t in todos)\nTODOS.update(todos)","typeGuard":"def is_allowed_status(s: object) -> bool:\n    return str(s).lower() in ('pending', 'in_progress', 'completed')","tryCatchPattern":"try:\n    TODOS.update(todos)\nexcept ValueError as e:\n    m = re.search(r\"invalid status '(.*?)'\", str(e))\n    if m and m.group(1) in MAP:\n        fixed = MAP[m.group(1)]\n        todos = [{**t, 'status': fixed} if str(t['status']).lower() == m.group(1) else t for t in todos]\n        TODOS.update(todos)\n    else:\n        raise","preventionTips":["Use an enum of exactly the three statuses in the schema","Normalize natural-language statuses via a mapping table","Casing is forgiven; wording and separators are not"],"tags":["todos","validation","enum"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}