TodoWrite and Task Management

The built-in task list. When to use it, when not to, and how it interacts with sub-agent task tracking.

Feature Foundational
9 min read
todowrite task-list planning sub-agents

What it is#

TodoWrite is Claude Code’s built-in task list. The model uses it to plan multi-step work, surface progress to the user, and keep itself on track across long sessions. The list is visible in the UI — each item has a state (pending, in-progress, completed) and a one-line description.

The list is not a project-management tool. It exists for the duration of the session, lives in the conversation state, and resets when the session ends. Its purpose is narrower and more useful than a generic todo system: it gives the model a visible scratchpad for “here is what I’m doing, here is what I’ve done”, and gives the user a way to see at a glance whether the model is on track or has lost the plot.

A good TodoWrite-driven session looks like a checklist that fills in left-to-right: items get added, one is in-progress at a time, completed items accumulate, and the user can see the model’s progress without reading every turn.

When to use it#

Use TodoWrite when the work has multiple non-trivial steps and the user benefits from seeing the structure. Concrete cases:

  • A task with three or more distinct steps. “Add a feature, write a test for it, run the test, commit” is borderline; “investigate, design, implement across N files, test, document” is where the list shines.
  • A task that may need to be paused or revisited. The list is the persistent reminder of “we were halfway through”.
  • A task where the order matters and the user might catch a sequencing mistake. Visible items let the user redirect before the model commits to the wrong order.
  • Anywhere the model is delegating sub-work to a sub-agent. Each delegated unit becomes a checklist item the parent can mark complete when the sub-agent returns.

Do not use TodoWrite for:

  • Single-step requests. “Read this file and tell me what it does” does not need a todo list. Ceremony for its own sake is friction.
  • Two-step trivial tasks. “Run the build and tell me if it succeeded” is one tool call and a sentence, not a checklist.
  • Conversations that are exploratory. When the user is thinking out loud and the path is not yet clear, the right move is a Plan-mode discussion, not a premature todo list.
  • Cross-session persistence. TodoWrite is session-scoped. For work that spans sessions, write a plan file or use memory.

How it works#

The data model#

Each todo item has three fields:

  • content — what gets shown to the model. The active phrasing, e.g. “Refactor the auth middleware to use the new session helper”.
  • activeForm — what gets shown to the user when the item is in-progress. The present participle, e.g. “Refactoring the auth middleware…”.
  • statuspending, in_progress, or completed.

The split between content and activeForm is what lets the UI show “Refactoring…” with a spinner instead of “Refactor” with no indication of state. It is small ceremony with a real readability payoff.

The “exactly one in-progress” invariant#

At any moment, exactly one item should be in_progress. The model marks the current item in-progress when it starts working on it, completes it before starting the next, and never has two items in-progress at the same time. This is what makes the list scannable — the in-progress item is always the answer to “what is the model doing right now?”.

Updating the list#

TodoWrite replaces the entire list each time it is called. The model passes the full set of items with their new statuses; the harness diffs and updates the UI. This is simpler than a per-item update API and matches how the model actually plans — re-deciding the whole list as new information arrives.

Interaction with sub-agents#

When the parent delegates to a sub-agent via the Task tool, the sub-agent has its own todo list that is independent of the parent’s. The parent sees the sub-agent’s summary on return, not its internal checklist. This is a feature: it prevents nested task lists from cluttering the parent’s view.

The parent typically tracks delegation as a single item (“Investigate auth-related files”) and marks it complete when the sub-agent returns.

Configuration#

TodoWrite has no configuration — it is always available and has no permission gates. The behaviour is shaped by convention rather than settings:

  • Use it for tasks with three or more distinct steps.
  • Mark exactly one item in-progress at a time.
  • Update the list as plans change — do not let it drift out of sync with what the model is actually doing.

There is no way to disable TodoWrite from settings.json; if a project wants the model to avoid the list, a CLAUDE.md line is the right place (“Prefer concise, single-turn responses. Avoid TodoWrite for simple tasks.”).

Examples#

A canonical multi-step task#

The user asks: “Migrate the API from v1 to v2 across the codebase.”

// First TodoWrite call
{
"tool": "TodoWrite",
"todos": [
{ "content": "Find every v1 API call site", "activeForm": "Finding v1 call sites", "status": "in_progress" },
{ "content": "Draft the migration plan", "activeForm": "Drafting the migration plan", "status": "pending" },
{ "content": "Apply edits in src/api/", "activeForm": "Applying edits in src/api/", "status": "pending" },
{ "content": "Update tests under tests/api/", "activeForm": "Updating tests", "status": "pending" },
{ "content": "Run the full test suite", "activeForm": "Running tests", "status": "pending" }
]
}

As work progresses, the model updates the list. The user can read the in-progress item at any moment to know where the session is.

Updating after discovery#

The investigation step finds that the v1 endpoint is also called from a config file the model did not initially consider. The list grows:

{
"tool": "TodoWrite",
"todos": [
{ "content": "Find every v1 API call site", "activeForm": "Finding v1 call sites", "status": "completed" },
{ "content": "Draft the migration plan", "activeForm": "Drafting the migration plan", "status": "in_progress" },
{ "content": "Apply edits in src/api/", "activeForm": "Applying edits in src/api/", "status": "pending" },
{ "content": "Update tests under tests/api/", "activeForm": "Updating tests", "status": "pending" },
{ "content": "Update config/legacy.yaml", "activeForm": "Updating legacy config", "status": "pending" },
{ "content": "Run the full test suite", "activeForm": "Running tests", "status": "pending" }
]
}

This is the right move: the list grew because the work grew. Hiding that from the user would be misleading.

Wrapping delegation as a single item#

When the parent delegates investigation to an Explore sub-agent, the parent’s list has one item for that delegation:

{
"todos": [
{ "content": "Map the auth module via Explore agent", "activeForm": "Mapping the auth module", "status": "in_progress" },
{ "content": "Apply the rename across found files", "activeForm": "Applying the rename", "status": "pending" }
]
}

The sub-agent runs its own internal plan; the parent does not need to mirror it.

Bad pattern — TodoWrite as decoration#

A single-step task with TodoWrite around it is friction, not value:

// BAD: this is one tool call wrapped in ceremony
{
"todos": [
{ "content": "Read the README", "activeForm": "Reading the README", "status": "in_progress" }
]
}

The model just reads the README and answers. No list needed.

Gotchas#

  • Stale lists confuse users. An item left in-progress after the model has already moved on suggests the model is stuck on something it has actually finished. Update the list every time the model’s focus shifts.
  • Two items in-progress is a bug. It tells the user the model is doing two things at once — which it is not, and which signals the model has lost track. Always one in-progress.
  • TodoWrite is not memory. A new session does not inherit the list. For cross-session work, write a plan file to the repo or use the memory system.
  • Adding many items up front is not the same as planning. A long list at turn one tells the user “the model thinks it knows the whole shape” — which is rarely true on non-trivial work. Better to start short and grow the list as the structure emerges.
  • Sub-agent lists do not leak into the parent. This is by design. If you want the parent to see what the sub-agent is doing, the sub-agent should return a summary that says so.
  • Items that get cancelled should be marked accordingly. Some workflows skip a planned step (e.g. tests already pass, no migration needed). Mark the item completed with a brief note, or remove it explicitly — do not leave it dangling.
  • Don’t use TodoWrite for one-step tasks. It is a tax on simple requests. The right reflex is “is the task multi-step and non-trivial?”; if not, skip the list.
  • Updating mid-tool-call confuses the UI. TodoWrite calls between tool calls are fine; mid-tool-call updates (somehow) are not. Update at natural step boundaries.

TodoWrite vs a plan file at a glance#

TodoWrite
  • Session-scoped, ephemeral
  • Three statuses: pending, in-progress, completed
  • Visible in the UI on every turn
  • Right for multi-step work in one session
  • Resets on new session
A plan file in the repo
  • Persistent across sessions
  • Free-form markdown — any structure
  • Only visible if the model reads it
  • Right for work that spans multiple sessions or contributors
  • Survives compaction and context resets
When the list itself becomes the deliverable

Sometimes the most valuable artifact of a session is the list. A user asks “scope this migration”; the model investigates and produces a six-item TodoWrite that captures the plan. The user accepts or amends the list, the model does not execute, the session ends. The list is what gets carried forward — into a plan file, into a tracking ticket, into a follow-up session. This is one of TodoWrite’s underused modes: the list is not always something the model fills in during a working session. Sometimes the list is the deliverable.

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.