Follow Discord
Sweep 22 Oct 2026 · 04:32Z Build v2.1.308 527 read Stable v2.1.308 Latest v2.1.308 Next v2.1.308 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Track todos changedagent-sdk/todo-tracking

Nearest release: v2.1.299, published 20 hours after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.

Upstream edited this page at 13 Oct 2026 04:17 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.

Upstream edited
Recorded here
Lines+296added
Lines−300removed
From line 8 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits9to this page, all time

The whole hunk

from line 8, old and new numbered
/
lines

The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.

from line 8
88 
99## Model availability
1010 
11<Note>
12 The following tools are available by default only on Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5, and earlier models. On every other model, including model IDs Claude Code doesn't recognize, they aren't available unless you opt in:
13 
14 * `TodoWrite`
15 * `TaskCreate`
16 * `TaskGet`
17 * `TaskUpdate`
18 * `TaskList`
19 
20 Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.
21 
22 This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.
23</Note>
11> [!NOTE]
12>
13> The following tools are available by default only on Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5, and earlier models. On every other model, including model IDs Claude Code doesn't recognize, they aren't available unless you opt in:
14>
15> * `TodoWrite`
16> * `TaskCreate`
17> * `TaskGet`
18> * `TaskUpdate`
19> * `TaskList`
20>
21> Wherever the tools are available, Claude Code provides the four Task tools, or `TodoWrite` instead when you set `CLAUDE_CODE_ENABLE_TASKS=0`.
22>
23> This default set applies in Claude Code v2.1.268 and later, which the TypeScript Agent SDK bundles from v0.3.268.
2424 
2525On a model that doesn't have the tools by default, unless you opt a session in, you see no `tool_use` blocks for them in the message stream. The Agent SDK applies these defaults through the Claude Code binary that it bundles. If you point `pathToClaudeCodeExecutable` (TypeScript) or `cli_path` (Python) at your own Claude Code install, you get whichever tools that install provides, under its own defaults. To see the exact set in a running session, [check which tools are available](/docs/en/tools-reference#check-which-tools-are-available). To opt a session in, do one of the following:
2626 
from line 56
5656* **Turn limit**: 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 the `error_max_turns` subtype. Check `subtype` to detect that ending.
5757* **Error handling**: 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](/docs/en/agent-sdk/agent-loop#handle-the-result) for the result subtypes.
5858 
59<Note>
60 The task system messages, [`SDKTaskNotificationMessage`](/docs/en/agent-sdk/typescript#sdktasknotificationmessage) (TypeScript) or [`TaskNotificationMessage`](/docs/en/agent-sdk/python#tasknotificationmessage) (Python) among them, report background tasks such as backgrounded commands and subagents. In the message stream, you see todo activity as `tool_use` blocks in the assistant messages.
61</Note>
59> [!NOTE]
60>
61> The task system messages, [`SDKTaskNotificationMessage`](/docs/en/agent-sdk/typescript#sdktasknotificationmessage) (TypeScript) or [`TaskNotificationMessage`](/docs/en/agent-sdk/python#tasknotificationmessage) (Python) among them, report background tasks such as backgrounded commands and subagents. In the message stream, you see todo activity as `tool_use` blocks in the assistant messages.
6262 
6363### Monitor todo changes
6464 
from line 66
6666 
6767The 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 both examples on this page do, rather than assuming the canonical name is always present.
6868 
69<CodeGroup>
70 ```typescript TypeScript theme={null}
71 import { query } from "@anthropic-ai/claude-agent-sdk";
72 
73 try {
74 for await (const message of query({
75 prompt: "Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
76 // Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
77 options: { maxTurns: 15, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
78 })) {
79 if (message.type !== "assistant") continue;
80 for (const block of message.message.content) {
81 if (block.type !== "tool_use") continue;
82 if (block.name === "TaskCreate") {
83 const input = block.input as { subject: string };
84 console.log(`+ ${input.subject}`);
85 } else if (block.name === "TaskUpdate") {
86 const input = block.input as {
87 taskId?: string;
88 id?: string;
89 task_id?: string;
90 status?: string;
91 };
92 const taskId = input.taskId ?? input.id ?? input.task_id;
93 if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`);
94 }
95 }
96 }
97 } catch (error) {
98 // A single-shot query() throws after yielding an error result.
99 console.log(`Session ended with an error: ${error}`);
100 }
101 ```
102 
103 ```python Python theme={null}
104 import asyncio
105 
106 from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
107 
108 async def main():
109 try:
110 async for message in query(
111 prompt="Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
112 # Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
113 options=ClaudeAgentOptions(max_turns=15, permission_mode="acceptEdits", env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}),
114 ):
115 if not isinstance(message, AssistantMessage):
116 continue
117 for block in message.content:
118 if not isinstance(block, ToolUseBlock):
119 continue
120 if block.name == "TaskCreate":
121 print(f"+ {block.input.get('subject', '')}")
122 elif block.name == "TaskUpdate" and block.input.get("status"):
123 task_id = (
124 block.input.get("taskId")
125 or block.input.get("id")
126 or block.input.get("task_id")
127 )
128 if task_id:
129 print(f" {task_id} -> {block.input['status']}")
130 except Exception as error:
131 # A single-shot query() raises after yielding an error result.
132 print(f"Session ended with an error: {error}")
133 
134 
135 asyncio.run(main())
136 ```
137</CodeGroup>
138 
139### Display progress in real time
140 
141The following example watches the assistant stream for `TaskCreate` and `TaskUpdate` `tool_use` blocks and keeps a map of tasks keyed by task ID in a `TaskTracker` class, rerendering a progress summary on every change. The summary counts completed and in-progress tasks and shows each active item's `activeForm` label in place of its `subject`. Use this shape when your application maintains a progress display instead of logging each event.
142 
143The assigned task ID isn't in the `TaskCreate` input. Claude Code delivers each tool's structured output on the user message that carries its `tool_result` block, in the `tool_use_result` field. For `TaskCreate`, that object is documented for TypeScript as `TaskCreateOutput` under [Tool Output Types](/docs/en/agent-sdk/typescript#tool-output-types), and in Python the field is a plain dict of the same shape. The tracker pairs each `tool_result` block with its `tool_use` call by `tool_use_id` and reads `task.id` from the paired message's `tool_use_result`. Claude can read the list back with `TaskList` and one task's full details with `TaskGet`.
144 
145<CodeGroup>
146 ```typescript TypeScript theme={null}
147 import { query } from "@anthropic-ai/claude-agent-sdk";
148 
149 type Task = { subject: string; activeForm?: string; status: string };
150 
151 class TaskTracker {
152 private tasks = new Map<string, Task>();
153 private pendingCreates = new Map<string, { subject: string; activeForm?: string }>();
154 
155 displayProgress() {
156 if (this.tasks.size === 0) {
157 console.log("\nProgress: no open tasks\n");
158 return;
159 }
160 
161 const items = [...this.tasks.values()];
162 const completed = items.filter((t) => t.status === "completed").length;
163 const inProgress = items.filter((t) => t.status === "in_progress").length;
164 
165 console.log(`\nProgress: ${completed}/${this.tasks.size} completed`);
166 console.log(`Currently working on: ${inProgress} task(s)\n`);
167 
168 for (const [id, task] of this.tasks) {
169 const icon =
170 task.status === "completed" ? "✅" : task.status === "in_progress" ? "🔧" : "❌";
171 const text = task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject;
172 console.log(`${id}. ${icon} ${text}`);
173 }
174 }
175 
176 handleToolUse(block: { id: string; name: string; input: unknown }) {
69```typescript TypeScript theme={null}
70import { query } from "@anthropic-ai/claude-agent-sdk";
71 
72try {
73 for await (const message of query({
74 prompt: "Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
75 // Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
76 options: { maxTurns: 15, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
77 })) {
78 if (message.type !== "assistant") continue;
79 for (const block of message.message.content) {
80 if (block.type !== "tool_use") continue;
17781 if (block.name === "TaskCreate") {
178 const input = block.input as { subject: string; activeForm?: string; active_form?: string };
179 this.pendingCreates.set(block.id, {
180 subject: input.subject,
181 activeForm: input.activeForm ?? input.active_form,
182 });
82 const input = block.input as { subject: string };
83 console.log(`+ ${input.subject}`);
18384 } else if (block.name === "TaskUpdate") {
18485 const input = block.input as {
18586 taskId?: string;
18687 id?: string;
18788 task_id?: string;
18889 status?: string;
189 activeForm?: string;
190 active_form?: string;
19190 };
19291 const taskId = input.taskId ?? input.id ?? input.task_id;
193 if (!taskId) return;
194 if (input.status === "deleted") {
195 this.tasks.delete(taskId);
196 this.displayProgress();
197 return;
198 }
199 const task = this.tasks.get(taskId);
200 if (!task) return;
201 if (input.status) task.status = input.status;
202 const active = input.activeForm ?? input.active_form;
203 if (active) task.activeForm = active;
92 if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`);
93 }
94 }
95 }
96} catch (error) {
97 // A single-shot query() throws after yielding an error result.
98 console.log(`Session ended with an error: ${error}`);
99}
100```
101 
102```python Python theme={null}
103import asyncio
104 
105from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
106 
107async def main():
108 try:
109 async for message in query(
110 prompt="Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
111 # Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
112 options=ClaudeAgentOptions(max_turns=15, permission_mode="acceptEdits", env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}),
113 ):
114 if not isinstance(message, AssistantMessage):
115 continue
116 for block in message.content:
117 if not isinstance(block, ToolUseBlock):
118 continue
119 if block.name == "TaskCreate":
120 print(f"+ {block.input.get('subject', '')}")
121 elif block.name == "TaskUpdate" and block.input.get("status"):
122 task_id = (
123 block.input.get("taskId")
124 or block.input.get("id")
125 or block.input.get("task_id")
126 )
127 if task_id:
128 print(f" {task_id} -> {block.input['status']}")
129 except Exception as error:
130 # A single-shot query() raises after yielding an error result.
131 print(f"Session ended with an error: {error}")
132 
133 
134asyncio.run(main())
135```
136 
137### Display progress in real time
138 
139The following example watches the assistant stream for `TaskCreate` and `TaskUpdate` `tool_use` blocks and keeps a map of tasks keyed by task ID in a `TaskTracker` class, rerendering a progress summary on every change. The summary counts completed and in-progress tasks and shows each active item's `activeForm` label in place of its `subject`. Use this shape when your application maintains a progress display instead of logging each event.
140 
141The assigned task ID isn't in the `TaskCreate` input. Claude Code delivers each tool's structured output on the user message that carries its `tool_result` block, in the `tool_use_result` field. For `TaskCreate`, that object is documented for TypeScript as `TaskCreateOutput` under [Tool Output Types](/docs/en/agent-sdk/typescript#tool-output-types), and in Python the field is a plain dict of the same shape. The tracker pairs each `tool_result` block with its `tool_use` call by `tool_use_id` and reads `task.id` from the paired message's `tool_use_result`. Claude can read the list back with `TaskList` and one task's full details with `TaskGet`.
142 
143```typescript TypeScript theme={null}
144import { query } from "@anthropic-ai/claude-agent-sdk";
145 
146type Task = { subject: string; activeForm?: string; status: string };
147 
148class TaskTracker {
149 private tasks = new Map<string, Task>();
150 private pendingCreates = new Map<string, { subject: string; activeForm?: string }>();
151 
152 displayProgress() {
153 if (this.tasks.size === 0) {
154 console.log("\nProgress: no open tasks\n");
155 return;
156 }
157 
158 const items = [...this.tasks.values()];
159 const completed = items.filter((t) => t.status === "completed").length;
160 const inProgress = items.filter((t) => t.status === "in_progress").length;
161 
162 console.log(`\nProgress: ${completed}/${this.tasks.size} completed`);
163 console.log(`Currently working on: ${inProgress} task(s)\n`);
164 
165 for (const [id, task] of this.tasks) {
166 const icon =
167 task.status === "completed" ? "✅" : task.status === "in_progress" ? "🔧" : "❌";
168 const text = task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject;
169 console.log(`${id}. ${icon} ${text}`);
170 }
171 }
172 
173 handleToolUse(block: { id: string; name: string; input: unknown }) {
174 if (block.name === "TaskCreate") {
175 const input = block.input as { subject: string; activeForm?: string; active_form?: string };
176 this.pendingCreates.set(block.id, {
177 subject: input.subject,
178 activeForm: input.activeForm ?? input.active_form,
179 });
180 } else if (block.name === "TaskUpdate") {
181 const input = block.input as {
182 taskId?: string;
183 id?: string;
184 task_id?: string;
185 status?: string;
186 activeForm?: string;
187 active_form?: string;
188 };
189 const taskId = input.taskId ?? input.id ?? input.task_id;
190 if (!taskId) return;
191 if (input.status === "deleted") {
192 this.tasks.delete(taskId);
204193 this.displayProgress();
194 return;
205195 }
206 }
207 
208 handleToolResult(block: { tool_use_id: string; is_error?: boolean }, result: unknown) {
209 const create = this.pendingCreates.get(block.tool_use_id);
210 if (!create) return;
211 this.pendingCreates.delete(block.tool_use_id);
212 if (block.is_error) return;
213 // The result's user message carries the tool's structured output as
214 // tool_use_result; for TaskCreate that's TaskCreateOutput,
215 // { task: { id, subject } }.
216 const out = result as { task?: { id: string } };
217 if (!out?.task?.id) return;
218 this.tasks.set(out.task.id, { ...create, status: "pending" });
196 const task = this.tasks.get(taskId);
197 if (!task) return;
198 if (input.status) task.status = input.status;
199 const active = input.activeForm ?? input.active_form;
200 if (active) task.activeForm = active;
219201 this.displayProgress();
220202 }
221 
222 async trackQuery(prompt: string) {
223 try {
224 for await (const message of query({
225 prompt,
226 options: { maxTurns: 20, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
227 })) {
228 if (message.type === "assistant") {
229 for (const block of message.message.content) {
230 if (block.type === "tool_use") this.handleToolUse(block);
231 }
232 }
233 if (message.type === "user" && Array.isArray(message.message.content)) {
234 for (const block of message.message.content) {
235 if (block.type === "tool_result") this.handleToolResult(block, message.tool_use_result);
236 }
203 }
204 
205 handleToolResult(block: { tool_use_id: string; is_error?: boolean }, result: unknown) {
206 const create = this.pendingCreates.get(block.tool_use_id);
207 if (!create) return;
208 this.pendingCreates.delete(block.tool_use_id);
209 if (block.is_error) return;
210 // The result's user message carries the tool's structured output as
211 // tool_use_result; for TaskCreate that's TaskCreateOutput,
212 // { task: { id, subject } }.
213 const out = result as { task?: { id: string } };
214 if (!out?.task?.id) return;
215 this.tasks.set(out.task.id, { ...create, status: "pending" });
216 this.displayProgress();
217 }
218 
219 async trackQuery(prompt: string) {
220 try {
221 for await (const message of query({
222 prompt,
223 options: { maxTurns: 20, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
224 })) {
225 if (message.type === "assistant") {
226 for (const block of message.message.content) {
227 if (block.type === "tool_use") this.handleToolUse(block);
237228 }
238229 }
239 } catch (error) {
240 // A single-shot query() throws after yielding an error result,
241 // such as when the maxTurns limit is hit.
242 console.log(`Session ended with an error: ${error}`);
230 if (message.type === "user" && Array.isArray(message.message.content)) {
231 for (const block of message.message.content) {
232 if (block.type === "tool_result") this.handleToolResult(block, message.tool_use_result);
233 }
234 }
243235 }
244 }
245 }
246 
247 // Usage
248 const tracker = new TaskTracker();
249 await tracker.trackQuery("Build a complete authentication system with todos");
250 ```
251 
252 ```python Python theme={null}
253 import asyncio
254 
255 from claude_agent_sdk import (
256 query,
257 ClaudeAgentOptions,
258 AssistantMessage,
259 UserMessage,
260 ToolUseBlock,
261 ToolResultBlock,
262 )
263 
264 
265 class TaskTracker:
266 def __init__(self):
267 self.tasks: dict[str, dict] = {}
268 self.pending_creates: dict[str, dict] = {}
269 
270 def display_progress(self):
271 if not self.tasks:
272 print("\nProgress: no open tasks\n")
273 return
274 
275 completed = len([t for t in self.tasks.values() if t["status"] == "completed"])
276 in_progress = len([t for t in self.tasks.values() if t["status"] == "in_progress"])
277 
278 print(f"\nProgress: {completed}/{len(self.tasks)} completed")
279 print(f"Currently working on: {in_progress} task(s)\n")
280 
281 for task_id, task in self.tasks.items():
282 icon = (
283 "✅"
284 if task["status"] == "completed"
285 else "🔧"
286 if task["status"] == "in_progress"
287 else "❌"
288 )
289 text = (
290 task["activeForm"]
291 if task["status"] == "in_progress" and task.get("activeForm")
292 else task["subject"]
293 )
294 print(f"{task_id}. {icon} {text}")
295 
296 def handle_tool_use(self, block: ToolUseBlock):
297 if block.name == "TaskCreate":
298 self.pending_creates[block.id] = {
299 "subject": block.input.get("subject", ""),
300 "activeForm": block.input.get("activeForm") or block.input.get("active_form"),
301 }
302 elif block.name == "TaskUpdate":
303 task_id = (
304 block.input.get("taskId")
305 or block.input.get("id")
306 or block.input.get("task_id")
307 )
308 if not task_id:
309 return
310 if block.input.get("status") == "deleted":
311 self.tasks.pop(task_id, None)
312 self.display_progress()
313 return
314 task = self.tasks.get(task_id)
315 if not task:
316 return
317 if block.input.get("status"):
318 task["status"] = block.input["status"]
319 active = block.input.get("activeForm") or block.input.get("active_form")
320 if active:
321 task["activeForm"] = active
322 self.display_progress()
323 
324 def handle_tool_result(self, block: ToolResultBlock, tool_use_result):
325 create = self.pending_creates.pop(block.tool_use_id, None)
326 if create is None or block.is_error:
327 return
328 # The result's user message carries the tool's structured output as
329 # tool_use_result; for TaskCreate that's {"task": {"id": ..., "subject": ...}}.
330 task = (tool_use_result or {}).get("task") or {}
331 if not task.get("id"):
332 return
333 self.tasks[task["id"]] = {**create, "status": "pending"}
334 self.display_progress()
335 
336 async def track_query(self, prompt: str):
337 try:
338 async for message in query(
339 prompt=prompt,
340 options=ClaudeAgentOptions(
341 max_turns=20,
342 permission_mode="acceptEdits",
343 env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"},
344 ),
345 ):
346 if isinstance(message, AssistantMessage):
347 for block in message.content:
348 if isinstance(block, ToolUseBlock):
349 self.handle_tool_use(block)
350 if isinstance(message, UserMessage) and isinstance(message.content, list):
351 for block in message.content:
352 if isinstance(block, ToolResultBlock):
353 self.handle_tool_result(block, message.tool_use_result)
354 except Exception as error:
355 # A single-shot query() raises after yielding an error result,
356 # such as when the max_turns limit is hit.
357 print(f"Session ended with an error: {error}")
358 
359 
360 # Usage
361 async def main():
362 tracker = TaskTracker()
363 await tracker.track_query("Build a complete authentication system with todos")
364 
365 
366 asyncio.run(main())
367 ```
368</CodeGroup>
236 } catch (error) {
237 // A single-shot query() throws after yielding an error result,
238 // such as when the maxTurns limit is hit.
239 console.log(`Session ended with an error: ${error}`);
240 }
241 }
242}
243 
244// Usage
245const tracker = new TaskTracker();
246await tracker.trackQuery("Build a complete authentication system with todos");
247```
248 
249```python Python theme={null}
250import asyncio
251 
252from claude_agent_sdk import (
253 query,
254 ClaudeAgentOptions,
255 AssistantMessage,
256 UserMessage,
257 ToolUseBlock,
258 ToolResultBlock,
259)
260 
261 
262class TaskTracker:
263 def __init__(self):
264 self.tasks: dict[str, dict] = {}
265 self.pending_creates: dict[str, dict] = {}
266 
267 def display_progress(self):
268 if not self.tasks:
269 print("\nProgress: no open tasks\n")
270 return
271 
272 completed = len([t for t in self.tasks.values() if t["status"] == "completed"])
273 in_progress = len([t for t in self.tasks.values() if t["status"] == "in_progress"])
274 
275 print(f"\nProgress: {completed}/{len(self.tasks)} completed")
276 print(f"Currently working on: {in_progress} task(s)\n")
277 
278 for task_id, task in self.tasks.items():
279 icon = (
280 "✅"
281 if task["status"] == "completed"
282 else "🔧"
283 if task["status"] == "in_progress"
284 else "❌"
285 )
286 text = (
287 task["activeForm"]
288 if task["status"] == "in_progress" and task.get("activeForm")
289 else task["subject"]
290 )
291 print(f"{task_id}. {icon} {text}")
292 
293 def handle_tool_use(self, block: ToolUseBlock):
294 if block.name == "TaskCreate":
295 self.pending_creates[block.id] = {
296 "subject": block.input.get("subject", ""),
297 "activeForm": block.input.get("activeForm") or block.input.get("active_form"),
298 }
299 elif block.name == "TaskUpdate":
300 task_id = (
301 block.input.get("taskId")
302 or block.input.get("id")
303 or block.input.get("task_id")
304 )
305 if not task_id:
306 return
307 if block.input.get("status") == "deleted":
308 self.tasks.pop(task_id, None)
309 self.display_progress()
310 return
311 task = self.tasks.get(task_id)
312 if not task:
313 return
314 if block.input.get("status"):
315 task["status"] = block.input["status"]
316 active = block.input.get("activeForm") or block.input.get("active_form")
317 if active:
318 task["activeForm"] = active
319 self.display_progress()
320 
321 def handle_tool_result(self, block: ToolResultBlock, tool_use_result):
322 create = self.pending_creates.pop(block.tool_use_id, None)
323 if create is None or block.is_error:
324 return
325 # The result's user message carries the tool's structured output as
326 # tool_use_result; for TaskCreate that's {"task": {"id": ..., "subject": ...}}.
327 task = (tool_use_result or {}).get("task") or {}
328 if not task.get("id"):
329 return
330 self.tasks[task["id"]] = {**create, "status": "pending"}
331 self.display_progress()
332 
333 async def track_query(self, prompt: str):
334 try:
335 async for message in query(
336 prompt=prompt,
337 options=ClaudeAgentOptions(
338 max_turns=20,
339 permission_mode="acceptEdits",
340 env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"},
341 ),
342 ):
343 if isinstance(message, AssistantMessage):
344 for block in message.content:
345 if isinstance(block, ToolUseBlock):
346 self.handle_tool_use(block)
347 if isinstance(message, UserMessage) and isinstance(message.content, list):
348 for block in message.content:
349 if isinstance(block, ToolResultBlock):
350 self.handle_tool_result(block, message.tool_use_result)
351 except Exception as error:
352 # A single-shot query() raises after yielding an error result,
353 # such as when the max_turns limit is hit.
354 print(f"Session ended with an error: {error}")
355 
356 
357# Usage
358async def main():
359 tracker = TaskTracker()
360 await tracker.track_query("Build a complete authentication system with todos")
361 
362 
363asyncio.run(main())
364```
369365 
370366## Related documentation
371367 
372368 
Feedback