shareAI-lab/learn-claude-code · error · ValueError

Item {item_id}: text required

Error message

Item {item_id}: text required

What it means

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.

Source

Thrown at agents/s03_todo_write.py:66

Prefer tools over prose."""


# -- TodoManager: structured state the LLM writes to --
class TodoManager:
    def __init__(self):
        self.items = []

    def update(self, items: list) -> str:
        if len(items) > 20:
            raise ValueError("Max 20 todos allowed")
        validated = []
        in_progress_count = 0
        for i, item in enumerate(items):
            text = str(item.get("text", "")).strip()
            status = str(item.get("status", "pending")).lower()
            item_id = str(item.get("id", str(i + 1)))
            if not text:
                raise ValueError(f"Item {item_id}: text required")
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Item {item_id}: invalid status '{status}'")
            if status == "in_progress":
                in_progress_count += 1
            validated.append({"id": item_id, "text": text, "status": status})
        if in_progress_count > 1:
            raise ValueError("Only one task can be in_progress at a time")
        self.items = validated
        return self.render()

    def render(self) -> str:
        if not self.items:
            return "No todos."
        lines = []
        for item in self.items:
            marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}[item["status"]]
            lines.append(f"{marker} #{item['id']}: {item['text']}")
        done = sum(1 for t in self.items if t["status"] == "completed")

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Include a non-empty `text` string on every item in every todo_update call, including items you are only marking completed
  2. Use the exact schema {id, text, status}; put any extra detail inside text, not sibling fields
  3. 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

Example fix

# before
[{"id": "2", "status": "completed"}]
# ValueError: Item 2: text required

# after
[{"id": "2", "text": "Write unit tests", "status": "completed"}]
Defensive patterns

Strategy: validation

Validate before calling

for i, item in enumerate(items):
    text = str(item.get("text", "")).strip()
    if not text:
        items[i] = {**item, "text": item.get("text") or f"task {i + 1}"}  # or reject before the call
# better: reject early
if any(not str(i.get("text", "")).strip() for i in items):
    raise SystemExit("todo payload has an empty text item")

Type guard

def is_nonempty_todo_item(item) -> bool:
    return (
        isinstance(item, dict)
        and isinstance(item.get("text", ""), str)
        and item["text"].strip() != ""
    )

Try / catch

try:
    TODO.update(items)
except ValueError as e:
    if "text required" in str(e):
        bad = str(e).split(":")[0].replace("Item ", "").strip()
        return f"Tool error: item {bad} lacks text. Re-submit full list with text on every item."
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/9d150f9602b0fa86. Report an issue: GitHub.