{"record":{"id":"9d150f9602b0fa86","repo":"shareAI-lab/learn-claude-code","slug":"item-item-id-text-required","errorCode":null,"errorMessage":"Item {item_id}: text required","messagePattern":"Item (.+?): text required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s03_todo_write.py","lineNumber":66,"sourceCode":"Prefer tools over prose.\"\"\"\n\n\n# -- TodoManager: structured state the LLM writes to --\nclass TodoManager:\n    def __init__(self):\n        self.items = []\n\n    def update(self, items: list) -> str:\n        if len(items) > 20:\n            raise ValueError(\"Max 20 todos allowed\")\n        validated = []\n        in_progress_count = 0\n        for i, item in enumerate(items):\n            text = str(item.get(\"text\", \"\")).strip()\n            status = str(item.get(\"status\", \"pending\")).lower()\n            item_id = str(item.get(\"id\", str(i + 1)))\n            if not text:\n                raise ValueError(f\"Item {item_id}: text required\")\n            if status not in (\"pending\", \"in_progress\", \"completed\"):\n                raise ValueError(f\"Item {item_id}: invalid status '{status}'\")\n            if status == \"in_progress\":\n                in_progress_count += 1\n            validated.append({\"id\": item_id, \"text\": text, \"status\": status})\n        if in_progress_count > 1:\n            raise ValueError(\"Only one task can be in_progress at a time\")\n        self.items = validated\n        return self.render()\n\n    def render(self) -> str:\n        if not self.items:\n            return \"No todos.\"\n        lines = []\n        for item in self.items:\n            marker = {\"pending\": \"[ ]\", \"in_progress\": \"[>]\", \"completed\": \"[x]\"}[item[\"status\"]]\n            lines.append(f\"{marker} #{item['id']}: {item['text']}\")\n        done = sum(1 for t in self.items if t[\"status\"] == \"completed\")","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s03_todo_write.py#L48-L84","documentation":"Raised by TodoManager.update() in agents/s03_todo_write.py:66 during per-item validation. Each item's `text` field is coerced to str and stripped; if the result is empty, the update is rejected with the offending item's id (falling back to its 1-based index when no id was supplied). A missing `text` key defaults to \"\" and fails the same way.","triggerScenarios":"The model sends a todo item as {\"id\": \"3\", \"status\": \"pending\"} with no text, or {\"text\": \"   \"} containing only whitespace, or text that stringifies to empty (e.g. an empty list). Any single bad item aborts the whole list update.","commonSituations":"Schema drift: the model invents fields like `description` or `title` instead of `text`. Status-first updates where the model intends to toggle status and forgets to carry the text forward (update() replaces the entire list, so every item must re-include its text). Whitespace-only entries from sloppy JSON generation.","solutions":["Include a non-empty `text` string on every item in every todo_update call, including items you are only marking completed","Use the exact schema {id, text, status}; put any extra detail inside text, not sibling fields","When the error names item N, check that item in your payload first — the id in the message is the submitted id or the 1-based index"],"exampleFix":"# before\n[{\"id\": \"2\", \"status\": \"completed\"}]\n# ValueError: Item 2: text required\n\n# after\n[{\"id\": \"2\", \"text\": \"Write unit tests\", \"status\": \"completed\"}]","handlingStrategy":"validation","validationCode":"for i, item in enumerate(items):\n    text = str(item.get(\"text\", \"\")).strip()\n    if not text:\n        items[i] = {**item, \"text\": item.get(\"text\") or f\"task {i + 1}\"}  # or reject before the call\n# better: reject early\nif any(not str(i.get(\"text\", \"\")).strip() for i in items):\n    raise SystemExit(\"todo payload has an empty text item\")","typeGuard":"def is_nonempty_todo_item(item) -> bool:\n    return (\n        isinstance(item, dict)\n        and isinstance(item.get(\"text\", \"\"), str)\n        and item[\"text\"].strip() != \"\"\n    )","tryCatchPattern":"try:\n    TODO.update(items)\nexcept ValueError as e:\n    if \"text required\" in str(e):\n        bad = str(e).split(\":\")[0].replace(\"Item \", \"\").strip()\n        return f\"Tool error: item {bad} lacks text. Re-submit full list with text on every item.\"\n    raise","preventionTips":["Always restate text for every item — statuses do not carry over between updates","Use the exact {id, text, status} keys; extra detail goes inside text","Validate the payload shape before the tool call instead of relying on the tool error to find bad items"],"tags":["validation","todo","schema","agent-tools","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}