{"record":{"id":"a22db88d031196fb","repo":"shareAI-lab/learn-claude-code","slug":"todos-index-requires-content","errorCode":null,"errorMessage":"todos[{index}] requires content","messagePattern":"todos\\[(.+?)\\] requires content","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s05_todo_write/code.py","lineNumber":138,"sourceCode":"                    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:\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:","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s05_todo_write/code.py#L120-L156","documentation":"Raised by TodoManager.update in s05_todo_write/code.py when a todo object's content is missing, empty, or whitespace-only after strip(). The index in the message refers to the element's position in the submitted list, letting callers pinpoint the offender. The batch is rejected atomically, so previous todos survive.","triggerScenarios":"An element like {\"status\": \"pending\"} with no content key, {\"content\": \"\"}, or {\"content\": \"   \"}. Note the module does not require activeForm (unlike s_full), only content and a valid status.","commonSituations":"LLM emitting a status-only item as a separator or header; whitespace content from trimmed plan text; optional-field confusion where the caller assumes content is optional.","solutions":["Give every item a descriptive non-empty content string","Filter empty items before the call: [t for t in todos if str(t.get('content', '')).strip()]","Use the reported index to fix just the offending element and resubmit the whole list"],"exampleFix":"// before\nTODOS.update([{\"status\": \"pending\"}])\n// after\nTODOS.update([{\"content\": \"write tests\", \"status\": \"pending\"}])","handlingStrategy":"validation","validationCode":"todos = [t for t in todos if str(t.get('content', '')).strip()]\nassert all(str(t.get('content', '')).strip() for t in todos)\nTODOS.update(todos)","typeGuard":"def has_content(todo: object) -> bool:\n    return isinstance(todo, dict) and bool(str(todo.get('content', '')).strip())","tryCatchPattern":"try:\n    TODOS.update(todos)\nexcept ValueError as e:\n    m = re.search(r\"todos\\[(\\d+)\\] requires content\", str(e))\n    if m:\n        i = int(m.group(1))\n        todos[i]['content'] = f\"(step {i})\"\n        TODOS.update(todos)\n    else:\n        raise","preventionTips":["Require content (minLength: 1) in the tool schema","Drop empty items before submission","Use the index in the message to locate the offender"],"tags":["todos","validation","agent-tools"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}