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
- 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
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
- 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
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
- Max 20 todos allowed
- Item {item_id}: invalid status '{status}'
- Only one task can be in_progress at a time
- Path escapes workspace: {p}
- Path escapes workspace: {p}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/9d150f9602b0fa86.
Report an issue: GitHub.