Repository navigation
feat: add permission_prompts option - #1477
model-bender wants to merge 1 commit into
Conversation
The TypeScript SDK's permissionPrompts option has no Python counterpart, so a host that wants prompts denied rather than answered had to reach for extra_args. With permission_prompts="none" the CLI runs with --permission-prompts none: the permission mode, rules and hooks still decide, anything that would prompt is denied at once, and can_use_tool is never called. "host" is the CLI's default and is passed through as given. As in the TypeScript SDK, the flag is sent only when the option is set. The e2e tests follow the verbatim_prompts layout: a control where the write asks can_use_tool and lands, then ClaudeSDKClient and query() runs with "none" where it doesn't.
| the default handler. | ||
| """ | ||
|
|
||
| permission_prompts: PermissionPrompts | None = None |
There was a problem hiding this comment.
ClaudeAgentOptions is a positional dataclass. Inserting this field before cwd shifts every subsequent constructor argument, so existing positional calls can bind a path to permission_prompts and pass it as --permission-prompts instead of setting the working directory. Could this new optional field be appended at the end (or made keyword-only) to preserve existing positional calls?
There was a problem hiding this comment.
Thanks for the comment. You're right that it shifts positional binding. I kept it next to permission_prompt_tool_name because that's how options get added here: strict_mcp_config, include_hook_events, forward_subagent_text, resume_drops_turn and verbatim_prompts all went in mid-class, and every ClaudeAgentOptions(...) call in the repo and docs uses keyword arguments. Making the class kw_only would be the real fix, but it changes the whole public constructor, so I'd keep that out of this PR. Happy to move the field to the end if the maintainers prefer.
There was a problem hiding this comment.
Thanks for explaining the existing convention, @model-bender. I agree making the entire dataclass keyword-only would be a separate, broader change. My remaining concern is that this is a public positional dataclass, so adding a field before cwd still silently changes the meaning of existing positional calls, even if the repository's own examples all use keywords. Appending this one field at the end preserves both those calls and every existing keyword call, with no change to the CLI behavior. Happy to leave that compatibility tradeoff to the maintainers; appreciate the discussion.
Re-opened from a new fork with the same commits as #1324, which closed when its fork was deleted. Earlier review is on #1324.
Summary
Adds
ClaudeAgentOptions.permission_prompts, the Python counterpart of the TypeScript SDK'spermissionPrompts, plus aPermissionPromptstype alias (Literal["host", "none"]) exported next toPermissionMode."host"(the CLI's default): this process answers prompts, throughcan_use_toolorpermission_prompt_tool_name."none": nobody does. The permission mode (including auto mode's classifier), rules and hooks still decide, anything that would otherwise prompt is denied at once with a message telling Claude the session has no approval surface, andcan_use_toolis never called. Useful for unattended runs, where a prompt nobody can answer is worse than a clear denial.When set, the SDK passes
--permission-prompts <value>; when unset, nothing is added, so existing behaviour is unchanged. This follows the TypeScript SDK (checked against@anthropic-ai/claude-agent-sdk0.3.283:if (permissionPrompts) args.push("--permission-prompts", permissionPrompts)). Until now Python users neededextra_args={"permission-prompts": "none"}.Verification
Real model calls. Bundled CLI version 2.1.283 through
ClaudeSDKClientwithmodel="haiku",permission_mode="default", acan_use_toolcallback that approves and records every call, and a prompt asking Claude to create a file with the Write tool:['Write']permission_prompts="host"['Write']permission_prompts="none"[]With
"none"the run still finishes cleanly (is_error=False; Claude replies that the tool was denied).New unit test
test_build_command_permission_prompts(tests/test_transport.py). With thesrc/changes stashed it fails (TypeError: ... unexpected keyword argument 'permission_prompts'); with them, it passes.New e2e tests in
e2e-tests/test_permission_prompts.py, laid out like theverbatim_promptsones: a control where the write askscan_use_tooland lands, thenClaudeSDKClientandquery()runs withpermission_prompts="none"where it doesn't. 3 passed against the real API.ruff check,ruff format --check,mypy src/ scripts/are clean.pytest tests/: 1588 passed, 6 skipped on Python 3.11, 3.12 and 3.13 (1587 passed, 7 skipped on 3.10, where the extra skip is a build test that needstomllib), and again withmcp==1.23.0.