shareAI-lab/learn-claude-code · error · ValueError
Item {item_id}: invalid status '{status}'
Error message
Item {item_id}: invalid status '{status}' What it means
Raised by TodoManager.update() in agents/s03_todo_write.py:68 when an item's `status` (lowercased, defaulted to "pending") is not one of the three allowed literals: pending, in_progress, completed. This is a closed-state-machine check — arbitrary progress labels like "done" or "in-progress" are rejected before the list is stored, so render()'s marker lookup ("[ ]", "[>]", "[x]") can never KeyError.
Source
Thrown at agents/s03_todo_write.py:68
# -- 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")
lines.append(f"\n({done}/{len(self.items)} completed)")
return "\n".join(lines)View on GitHub (pinned to 985456f4ad)
Solutions
- Use exactly one of: "pending", "in_progress", "completed" (lowercase, underscore in the middle)
- Map model vocabulary to schema at the tool layer if you control the harness: normalize "done"->"completed", "in-progress"->"in_progress", or reject with a schema echo
Example fix
# before
{"id": "1", "text": "ship it", "status": "done"}
# ValueError: Item 1: invalid status 'done'
# after
{"id": "1", "text": "ship it", "status": "completed"} Defensive patterns
Strategy: validation
Validate before calling
ALLOWED = {"pending", "in_progress", "completed"}
items = [
{**i, "status": str(i.get("status", "pending")).strip().lower().replace("-", "_").replace(" ", "_")}
for i in items
]
assert all(i["status"] in ALLOWED for i in items), f"bad status in {[i['status'] for i in items if i['status'] not in ALLOWED]}" Type guard
TODO_STATUSES = frozenset(("pending", "in_progress", "completed"))
def is_valid_todo_status(s: object) -> bool:
return isinstance(s, str) and s.lower() in TODO_STATUSES Try / catch
try:
TODO.update(items)
except ValueError as e:
if "invalid status" in str(e):
return f"Tool error: {e}. Allowed: pending, in_progress, completed (lowercase, underscore)."
raise Prevention
- Normalize status strings (strip, lower, hyphen/space -> underscore) before submitting
- Beware 'done' — map it to 'completed' yourself
- There is no blocked/cancelled state; model that via text, not status
When it happens
Trigger: The model sends status "done", "Done" is fine (lowercased) but "finished", "in-progress" (hyphen instead of underscore), "in progress" (space), or "blocked" all fail. A missing status defaults safely to pending, so the error only comes from a present-but-wrong value.
Common situations: "done" is the single most common LLM slip. Hyphen/space variants of in_progress. Models adding custom statuses like "blocked" or "cancelled" that the harness does not model.
Related errors
- Item {item_id}: text required
- Only one task can be in_progress at a time
- Max 20 todos allowed
- Invalid status: {status}
- Invalid status: {status}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/2fa67fd9cb385b77.
Report an issue: GitHub.