Follow Discord
Sweep 03 Nov 2026 · 22:27Z Build v2.1.318 536 read Stable v2.1.311 Latest v2.1.318 Next v2.1.318 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Handle approvals and user input changedagent-sdk/user-input

Nearest release: v2.1.295, published 9 hours before 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 9 Oct 2026 03:28 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 30 Oct 2026 18:07 UTC.

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

The whole hunk

from line 221, old and new numbered
/
lines
from line 221
221221 
222222The `ask_user` and `askUser` helpers in the following snippets stand in for your application's own prompt UI.
223223 
224**Approve**
224> [tab] Approve
225>
226> The user approves the action as-is. Pass through the `input` from your callback unchanged and the tool executes exactly as Claude requested.
227>
228> ```python Python theme={null}
229> async def can_use_tool(tool_name, input_data, context):
230> print(f"Claude wants to use {tool_name}")
231> approved = await ask_user("Allow this action?")
232>
233> if approved:
234> return PermissionResultAllow(updated_input=input_data)
235> return PermissionResultDeny(message="User declined")
236> ```
237>
238> ```typescript TypeScript theme={null}
239> canUseTool: async (toolName, input) => {
240> console.log(`Claude wants to use ${toolName}`);
241> const approved = await askUser("Allow this action?");
242>
243> if (approved) {
244> return { behavior: "allow", updatedInput: input };
245> }
246> return { behavior: "deny", message: "User declined" };
247> };
248> ```
225249 
226The user approves the action as-is. Pass through the `input` from your callback unchanged and the tool executes exactly as Claude requested.
250> [tab] Approve with changes
251>
252> The user approves but wants to modify the request first. You can change the input before the tool executes. Claude sees the result but isn't told you changed anything. Useful for sanitizing parameters, adding constraints, or scoping access.
253>
254> ```python Python theme={null}
255> async def can_use_tool(tool_name, input_data, context):
256> if tool_name == "Bash":
257> # User approved, but scope all commands to sandbox
258> sandboxed_input = {**input_data}
259> sandboxed_input["command"] = input_data["command"].replace(
260> "/tmp", "/tmp/sandbox"
261> )
262> return PermissionResultAllow(updated_input=sandboxed_input)
263> return PermissionResultAllow(updated_input=input_data)
264> ```
265>
266> ```typescript TypeScript theme={null}
267> canUseTool: async (toolName, input) => {
268> if (toolName === "Bash") {
269> // User approved, but scope all commands to sandbox
270> const sandboxedInput = {
271> ...input,
272> command: input.command.replace("/tmp", "/tmp/sandbox")
273> };
274> return { behavior: "allow", updatedInput: sandboxedInput };
275> }
276> return { behavior: "allow", updatedInput: input };
277> };
278> ```
227279 
228```python Python theme={null}
229async def can_use_tool(tool_name, input_data, context):
230 print(f"Claude wants to use {tool_name}")
231 approved = await ask_user("Allow this action?")
280> [tab] Approve and remember
281>
282> The user approves and doesn't want to be asked again for this kind of call. The third callback argument carries `suggestions`, an array of ready-made [`PermissionUpdate`](https://code.claude.com/docs/en/agent-sdk/typescript#permissionupdate) entries. Echo one back in `updatedPermissions` to apply it. A suggestion with the `localSettings` destination writes the rule to `.claude/settings.local.json` so future sessions skip the prompt for matching calls.
283>
284> In TypeScript, skip the always-allow choice for a request whose options carry [`suppressAlwaysAllowRule: true`](https://code.claude.com/docs/en/agent-sdk/typescript#canusetool). The hint requires Agent SDK v0.3.268 or later, and the Python `context` doesn't carry it.
285>
286> The Python example requires `claude-agent-sdk` 0.1.80 or later.
287>
288> ```python Python theme={null}
289> async def can_use_tool(tool_name, input_data, context):
290> choice = await ask_user(f"Allow {tool_name}?", ["once", "always", "no"])
291>
292> if choice == "always":
293> persist = [
294> s for s in context.suggestions if s.destination == "localSettings"
295> ]
296> return PermissionResultAllow(
297> updated_input=input_data, updated_permissions=persist
298> )
299> if choice == "once":
300> return PermissionResultAllow(updated_input=input_data)
301> return PermissionResultDeny(message="User declined")
302> ```
303>
304> ```typescript TypeScript theme={null}
305> canUseTool: async (toolName, input, { suggestions = [] }) => {
306> const choice = await askUser(`Allow ${toolName}?`, ["once", "always", "no"]);
307>
308> if (choice === "always") {
309> const persist = suggestions.filter(
310> (s) => s.destination === "localSettings"
311> );
312> return {
313> behavior: "allow",
314> updatedInput: input,
315> updatedPermissions: persist
316> };
317> }
318> if (choice === "once") {
319> return { behavior: "allow", updatedInput: input };
320> }
321> return { behavior: "deny", message: "User declined" };
322> };
323> ```
232324 
233 if approved:
234 return PermissionResultAllow(updated_input=input_data)
235 return PermissionResultDeny(message="User declined")
236```
325> [tab] Reject
326>
327> The user doesn't want this action to happen. Block the tool and provide a message explaining why. Claude sees this message and may try a different approach.
328>
329> ```python Python theme={null}
330> async def can_use_tool(tool_name, input_data, context):
331> approved = await ask_user(f"Allow {tool_name}?")
332>
333> if not approved:
334> return PermissionResultDeny(message="User rejected this action")
335> return PermissionResultAllow(updated_input=input_data)
336> ```
337>
338> ```typescript TypeScript theme={null}
339> canUseTool: async (toolName, input) => {
340> const approved = await askUser(`Allow ${toolName}?`);
341>
342> if (!approved) {
343> return {
344> behavior: "deny",
345> message: "User rejected this action"
346> };
347> }
348> return { behavior: "allow", updatedInput: input };
349> };
350> ```
237351 
238```typescript TypeScript theme={null}
239canUseTool: async (toolName, input) => {
240 console.log(`Claude wants to use ${toolName}`);
241 const approved = await askUser("Allow this action?");
352> [tab] Suggest alternative
353>
354> The user doesn't want this specific action, but has a different idea. Block the tool and include guidance in your message. Claude will read this and decide how to proceed based on your feedback.
355>
356> ```python Python theme={null}
357> async def can_use_tool(tool_name, input_data, context):
358> if tool_name == "Bash" and "rm" in input_data.get("command", ""):
359> # User doesn't want to delete, suggest archiving instead
360> return PermissionResultDeny(
361> message="User doesn't want to delete files. They asked if you could compress them into an archive instead."
362> )
363> return PermissionResultAllow(updated_input=input_data)
364> ```
365>
366> ```typescript TypeScript theme={null}
367> canUseTool: async (toolName, input) => {
368> if (toolName === "Bash" && input.command.includes("rm")) {
369> // User doesn't want to delete, suggest archiving instead
370> return {
371> behavior: "deny",
372> message:
373> "User doesn't want to delete files. They asked if you could compress them into an archive instead."
374> };
375> }
376> return { behavior: "allow", updatedInput: input };
377> };
378> ```
242379 
243 if (approved) {
244 return { behavior: "allow", updatedInput: input };
245 }
246 return { behavior: "deny", message: "User declined" };
247};
248```
249 
250**Approve with changes**
251 
252The user approves but wants to modify the request first. You can change the input before the tool executes. Claude sees the result but isn't told you changed anything. Useful for sanitizing parameters, adding constraints, or scoping access.
253 
254```python Python theme={null}
255async def can_use_tool(tool_name, input_data, context):
256 if tool_name == "Bash":
257 # User approved, but scope all commands to sandbox
258 sandboxed_input = {**input_data}
259 sandboxed_input["command"] = input_data["command"].replace(
260 "/tmp", "/tmp/sandbox"
261 )
262 return PermissionResultAllow(updated_input=sandboxed_input)
263 return PermissionResultAllow(updated_input=input_data)
264```
265 
266```typescript TypeScript theme={null}
267canUseTool: async (toolName, input) => {
268 if (toolName === "Bash") {
269 // User approved, but scope all commands to sandbox
270 const sandboxedInput = {
271 ...input,
272 command: input.command.replace("/tmp", "/tmp/sandbox")
273 };
274 return { behavior: "allow", updatedInput: sandboxedInput };
275 }
276 return { behavior: "allow", updatedInput: input };
277};
278```
279 
280**Approve and remember**
281 
282The user approves and doesn't want to be asked again for this kind of call. The third callback argument carries `suggestions`, an array of ready-made [`PermissionUpdate`](https://code.claude.com/docs/en/agent-sdk/typescript#permissionupdate) entries. Echo one back in `updatedPermissions` to apply it. A suggestion with the `localSettings` destination writes the rule to `.claude/settings.local.json` so future sessions skip the prompt for matching calls.
283 
284In TypeScript, skip the always-allow choice for a request whose options carry [`suppressAlwaysAllowRule: true`](https://code.claude.com/docs/en/agent-sdk/typescript#canusetool). The hint requires Agent SDK v0.3.268 or later, and the Python `context` doesn't carry it.
285 
286The Python example requires `claude-agent-sdk` 0.1.80 or later.
287 
288```python Python theme={null}
289async def can_use_tool(tool_name, input_data, context):
290 choice = await ask_user(f"Allow {tool_name}?", ["once", "always", "no"])
291 
292 if choice == "always":
293 persist = [
294 s for s in context.suggestions if s.destination == "localSettings"
295 ]
296 return PermissionResultAllow(
297 updated_input=input_data, updated_permissions=persist
298 )
299 if choice == "once":
300 return PermissionResultAllow(updated_input=input_data)
301 return PermissionResultDeny(message="User declined")
302```
303 
304```typescript TypeScript theme={null}
305canUseTool: async (toolName, input, { suggestions = [] }) => {
306 const choice = await askUser(`Allow ${toolName}?`, ["once", "always", "no"]);
307 
308 if (choice === "always") {
309 const persist = suggestions.filter(
310 (s) => s.destination === "localSettings"
311 );
312 return {
313 behavior: "allow",
314 updatedInput: input,
315 updatedPermissions: persist
316 };
317 }
318 if (choice === "once") {
319 return { behavior: "allow", updatedInput: input };
320 }
321 return { behavior: "deny", message: "User declined" };
322};
323```
324 
325**Reject**
326 
327The user doesn't want this action to happen. Block the tool and provide a message explaining why. Claude sees this message and may try a different approach.
328 
329```python Python theme={null}
330async def can_use_tool(tool_name, input_data, context):
331 approved = await ask_user(f"Allow {tool_name}?")
332 
333 if not approved:
334 return PermissionResultDeny(message="User rejected this action")
335 return PermissionResultAllow(updated_input=input_data)
336```
337 
338```typescript TypeScript theme={null}
339canUseTool: async (toolName, input) => {
340 const approved = await askUser(`Allow ${toolName}?`);
341 
342 if (!approved) {
343 return {
344 behavior: "deny",
345 message: "User rejected this action"
346 };
347 }
348 return { behavior: "allow", updatedInput: input };
349};
350```
351 
352**Suggest alternative**
353 
354The user doesn't want this specific action, but has a different idea. Block the tool and include guidance in your message. Claude will read this and decide how to proceed based on your feedback.
355 
356```python Python theme={null}
357async def can_use_tool(tool_name, input_data, context):
358 if tool_name == "Bash" and "rm" in input_data.get("command", ""):
359 # User doesn't want to delete, suggest archiving instead
360 return PermissionResultDeny(
361 message="User doesn't want to delete files. They asked if you could compress them into an archive instead."
362 )
363 return PermissionResultAllow(updated_input=input_data)
364```
365 
366```typescript TypeScript theme={null}
367canUseTool: async (toolName, input) => {
368 if (toolName === "Bash" && input.command.includes("rm")) {
369 // User doesn't want to delete, suggest archiving instead
370 return {
371 behavior: "deny",
372 message:
373 "User doesn't want to delete files. They asked if you could compress them into an archive instead."
374 };
375 }
376 return { behavior: "allow", updatedInput: input };
377};
378```
379 
380**Redirect entirely**
381 
382For a complete change of direction (not just a nudge), use [streaming input](https://code.claude.com/docs/en/agent-sdk/streaming-vs-single-mode.md) to send Claude a new instruction directly. This bypasses the current tool request and gives Claude entirely new instructions to follow.
380> [tab] Redirect entirely
381>
382> For a complete change of direction (not just a nudge), use [streaming input](https://code.claude.com/docs/en/agent-sdk/streaming-vs-single-mode.md) to send Claude a new instruction directly. This bypasses the current tool request and gives Claude entirely new instructions to follow.
383383 
384384## Handle clarifying questions
385385 
Feedback