{"record":{"id":"b1b165295f688f9e","repo":"shareAI-lab/learn-claude-code","slug":"item-i-invalid-status-status","errorCode":null,"errorMessage":"Item {i}: invalid status '{status}'","messagePattern":"Item (.+?): invalid status '(.+?)'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s_full.py","lineNumber":135,"sourceCode":"        return f\"Edited {path}\"\n    except Exception as e:\n        return f\"Error: {e}\"\n\n\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)","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s_full.py#L117-L153","documentation":"Raised by TodoManager.update in s_full.py when an item's status (lowercased string of item.get('status', 'pending')) is not one of pending/in_progress/completed. The value is coerced with str() before comparison, so non-string statuses like booleans or ints also fail after coercion ('true', '1'). The failing index i and the bad value are both included in the message.","triggerScenarios":"Passing status \"done\", \"in-progress\" (hyphen), \"In Progress\" (this one actually works because of .lower(), but \"in progress\" with a space fails), status True (coerces to \"true\"), or status 2. Each produces the message with the coerced string shown.","commonSituations":"LLMs writing natural-language statuses like \"done\" or \"in review\"; camelCase \"inProgress\" (lowercases to \"inprogress\", still invalid); enum values copied from a different todo system's schema.","solutions":["Map your statuses to the allowed set before update: {'done': 'completed', 'doing': 'in_progress', 'todo': 'pending'}","Validate with status in ('pending', 'in_progress', 'completed') client-side and reject early","Note that casing is tolerated (values are lowercased) but word separators are not — use snake_case"],"exampleFix":"// before\nTODOS.update([{\"content\": \"x\", \"status\": \"done\", \"activeForm\": \"\"}])\n// after\nTODOS.update([{\"content\": \"x\", \"status\": \"completed\", \"activeForm\": \"\"}])","handlingStrategy":"validation","validationCode":"ALLOWED = ('pending', 'in_progress', 'completed')\nMAP = {'done': 'completed', 'todo': 'pending', 'doing': 'in_progress', 'inprogress': 'in_progress'}\n\nitems = [{**it, 'status': MAP.get(str(it.get('status', 'pending')).lower(), str(it.get('status', 'pending')).lower())} for it in items]\nassert all(it['status'] in ALLOWED for it in items)\nTODOS.update(items)","typeGuard":"def is_valid_status(s: object) -> bool:\n    return str(s if s is not None else 'pending').lower() in ('pending', 'in_progress', 'completed')","tryCatchPattern":"try:\n    TODOS.update(items)\nexcept ValueError as e:\n    m = re.search(r\"invalid status '(.*?)'\", str(e))\n    if m:\n        fixed = MAP.get(m.group(1))\n        if fixed:\n            items = [{**it, 'status': fixed} if str(it['status']).lower() == m.group(1) else it for it in items]\n            TODOS.update(items)\n        else:\n            raise\n    else:\n        raise","preventionTips":["Use an enum in the tool schema with only the three allowed values","Map external/natural-language statuses before calling update","Remember casing is normalized but separators are not"],"tags":["todos","validation","enum","agent-tools"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}