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.
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…”.status—pending,in_progress, orcompleted.
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#
- 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
- 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
Related features#
- The Task Tool — Background Work
- Plan Mode
- Sub-Agents
- Conversation-Driven Development
- The Conversation Loop
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.