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

Max 20 todos

Error message

Max 20 todos

What it means

Raised by TodoManager.update in s_full.py when the validated list contains more than 20 items. The cap is a hard limit applied after per-item validation, forcing the caller to keep plans compact. The whole update is atomic: the error means self.items still holds the previous list.

Source

Thrown at agents/s_full.py:139

# === SECTION: todos (s03) ===
class TodoManager:
    def __init__(self):
        self.items = []

    def update(self, items: list) -> str:
        validated, ip = [], 0
        for i, item in enumerate(items):
            content = str(item.get("content", "")).strip()
            status = str(item.get("status", "pending")).lower()
            af = str(item.get("activeForm", "")).strip()
            if not content: raise ValueError(f"Item {i}: content required")
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Item {i}: invalid status '{status}'")
            if not af: raise ValueError(f"Item {i}: activeForm required")
            if status == "in_progress": ip += 1
            validated.append({"content": content, "status": status, "activeForm": af})
        if len(validated) > 20: raise ValueError("Max 20 todos")
        if ip > 1: raise ValueError("Only one in_progress allowed")
        self.items = validated
        return self.render()

    def render(self) -> str:
        if not self.items: return "No todos."
        lines = []
        for item in self.items:
            m = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}.get(item["status"], "[?]")
            suffix = f" <- {item['activeForm']}" if item["status"] == "in_progress" else ""
            lines.append(f"{m} {item['content']}{suffix}")
        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)

    def has_open_items(self) -> bool:
        return any(item.get("status") != "completed" for item in self.items)

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Consolidate related steps into broader items so the list stays at or under 20
  2. Split work into phases and replace the list per phase rather than accumulating items
  3. Check len(items) <= 20 before calling update and trim/merge client-side

Example fix

// before
TODOS.update(items)  # items has 23 entries
// after
merged = merge_related_steps(items)  # <= 20 entries
TODOS.update(merged)
Defensive patterns

Strategy: validation

Validate before calling

if len(items) > 20:
    items = merge_related_steps(items)[:20]  # or split into phases
TODOS.update(items)

Prevention

When it happens

Trigger: Submitting 21+ todo items in one update() call, e.g. an agent decomposing a large task into one item per subtask and exceeding the cap by a single item.

Common situations: Over-granular planning (one todo per file instead of per feature); appending new steps to an existing 20-item list instead of replacing it; migrating a long external checklist into the todo tool verbatim.

Related errors


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