As of TypeScript Agent SDK 0.3.142 and Claude Code v2.1.142, sessions use the structured Task tools
TaskCreate, TaskUpdate, TaskGet, and TaskList instead of TodoWrite. The Python SDK gets this change from the Claude Code CLI it launches, not from the Python package version: the switch applies once that CLI — the copy bundled inside the pip package, or one you point to with cli_path — is v2.1.142 or later. See Migrate to Task tools for how monitoring code changes. The examples on this page set CLAUDE_CODE_ENABLE_TASKS=0 to keep showing TodoWrite for sessions that have not migrated yet.Todo Lifecycle
Todos follow a predictable lifecycle:- Created as
pendingwhen tasks are identified - Activated to
in_progresswhen work begins - Completed when the task finishes successfully
- Removed when all tasks in a group are completed
When Todos Are Used
The SDK creates todos for most multi-step work, such as:- Complex multi-step tasks requiring 3 or more distinct actions
- User-provided task lists when multiple items are mentioned
- Non-trivial operations that benefit from progress tracking
- Explicit requests when users ask for todo organization
Examples
Before running these examples, install the Claude Agent SDK by following the quickstart. Each example runs until the agent finishes and yields its final result message. If a session reaches its turn limit first, that result message has theerror_max_turns subtype. Check subtype to detect that ending.
These examples use single-shot query() calls. After yielding an error_max_turns result, query() raises an error that includes Reached maximum number of turns. Each example wraps its loop in a try block to exit cleanly when that happens.
See Handle the result for the result subtypes.
Monitoring Todo Changes
Real-time Progress Display
Migrate to Task tools
The Task tools split the singleTodoWrite call into TaskCreate for each new item and TaskUpdate for each status change, with TaskList and TaskGet available for the model to read back the current list. Your monitoring code still inspects tool_use blocks in the assistant stream, but maintains a map keyed by task ID instead of replacing the whole list on every call. The Task tools are the default as of TypeScript Agent SDK 0.3.142 and Claude Code v2.1.142, so no options.env change is needed.
With TodoWrite | With Task tools |
|---|---|
One tool call rewrites the full todos array | TaskCreate adds one item, TaskUpdate patches one item by taskId |
Match block.name === "TodoWrite" | Match block.name === "TaskCreate" or "TaskUpdate" |
Item shape: { content, status, activeForm } | TaskCreate input: { subject, description, activeForm?, metadata? }. TaskUpdate input: { taskId, status?, subject?, description?, activeForm?, addBlocks?, addBlockedBy?, owner?, metadata? }. status is "pending", "in_progress", or "completed"; set status: "deleted" to delete |
Render block.input.todos directly | Accumulate items across calls, or read a snapshot from a TaskList tool result |
TaskCreate input. It comes back in the matching tool_result as { task: { id, subject } }, so capture it from the result block to key your map. The following example shows the minimal change to the Monitoring Todo Changes loop. It reads only tool_use inputs and skips capturing IDs from tool_result blocks. To render a complete list, watch for a TaskList tool result in the stream or accumulate TaskCreate results and TaskUpdate inputs into a map.
The streamed tool_use input is the raw shape the model emitted. Claude Code repairs some close-but-incorrect key names before execution, mapping id or task_id to taskId and active_form to activeForm, but that repair is not reflected in the stream. Read TaskUpdate input fields defensively, as the samples below do, rather than assuming the canonical name is always present.