Permissions
View SourceControl how your agent uses tools with permission modes, hooks, and declarative allow/deny rules.
Official Documentation: This guide is based on the official Claude Agent SDK documentation. Examples are adapted for Elixir.
The Elixir SDK provides permission controls to manage how Claude uses tools. Use permission modes and tool rules to define what is allowed automatically, and the :can_use_tool callback to handle everything else at runtime.
This page covers permission modes and rules. To build interactive approval flows where users approve or deny tool requests at runtime, see Handle approvals and user input.
How permissions are evaluated
When Claude requests a tool, the SDK checks permissions in this order:
- Hooks -- Run hooks first, which can allow, deny, or continue to the next step.
- Deny rules -- Check
denyrules (from:disallowed_toolsand settings.json). If a deny rule matches, the tool is blocked, even in:bypass_permissionsmode. - Permission mode -- Apply the active permission mode.
:bypass_permissionsapproves everything that reaches this step.:accept_editsapproves file operations. Other modes fall through. - Allow rules -- Check
allowrules (from:allowed_toolsand settings.json). If a rule matches, the tool is approved. can_use_toolcallback -- If not resolved by any of the above, call your:can_use_toolcallback for a decision. This can be a module implementingClaudeCode.Hookor a 2-arity function. In:dont_askmode, this step is skipped and the tool is denied.
This page focuses on allow and deny rules and permission modes. For the other steps:
- Hooks: run custom code to allow, deny, or modify tool requests. See Control execution with hooks.
can_use_toolcallback: prompt users for approval at runtime or implement programmatic decisions. See Hooks: can_use_tool and Handle approvals and user input.
Allow and deny rules
:allowed_tools and :disallowed_tools add entries to the allow and deny rule lists in the evaluation flow above. They control whether a tool call is approved, not whether the tool is available to Claude.
| Option | Effect |
|---|---|
allowed_tools: ["Read", "Grep"] | Read and Grep are auto-approved. Tools not listed here still exist and fall through to the permission mode and can_use_tool. |
disallowed_tools: ["Bash"] | Bash is always denied. Deny rules are checked first and hold in every permission mode, including :bypass_permissions. |
For a locked-down agent, pair :allowed_tools with permission_mode: :dont_ask. Listed tools are approved; anything else is denied outright instead of prompting:
{:ok, result} = ClaudeCode.query(
"Analyze this codebase",
allowed_tools: ["Read", "Glob", "Grep"],
permission_mode: :dont_ask
)Warning:
:allowed_toolsdoes not constrain:bypass_permissions.:allowed_toolsonly pre-approves the tools you list. Unlisted tools are not matched by any allow rule and fall through to the permission mode, where:bypass_permissionsapproves them. Settingallowed_tools: ["Read"]alongsidepermission_mode: :bypass_permissionsstill approves every tool, includingBash,Write, andEdit. If you need:bypass_permissionsbut want specific tools blocked, use:disallowed_tools.
You can also configure allow, deny, and ask rules declaratively in .claude/settings.json. The SDK does not load filesystem settings by default, so you must set setting_sources: ["project"] in your options for these rules to apply. See Permission settings for the rule syntax.
Permission modes
Permission modes provide global control over how Claude uses tools. You can set the permission mode when calling ClaudeCode.query/2, when starting a session, or change it dynamically during streaming sessions.
Available modes
The SDK supports these permission modes:
| Mode | Description | Tool behavior |
|---|---|---|
:default | Standard permission behavior | No auto-approvals; unmatched tools trigger your can_use_tool callback or are rejected |
:accept_edits | Auto-accept file edits | File edits and filesystem operations (mkdir, rm, mv, etc.) are automatically approved |
:bypass_permissions | Bypass all permission checks | All tools run without permission prompts (use with caution) |
:plan | Planning mode | No tool execution; Claude plans without making changes |
:delegate | Delegate to permission tool | All permission decisions are delegated to your can_use_tool callback |
:dont_ask | Deny unmatched tools | Tools not explicitly allowed by rules are denied without prompting |
Warning: When using
:bypass_permissions, all subagents inherit this mode and it cannot be overridden. Subagents may have different system prompts and less constrained behavior than your main agent. Enabling:bypass_permissionsgrants them full, autonomous system access without any approval prompts.
Set permission mode
You can set the permission mode once when starting a query or session, or change it dynamically while the session is active.
At session start:
{:ok, session} = ClaudeCode.start_link(
permission_mode: :accept_edits
)Per-query override:
# Session starts in default mode
{:ok, session} = ClaudeCode.start_link(permission_mode: :default)
# Override to accept_edits for this specific query
session
|> ClaudeCode.stream("Refactor this module", permission_mode: :accept_edits)
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1)During streaming (dynamic change):
Call ClaudeCode.Session.set_permission_mode/2 to change the mode mid-session. The new mode takes effect immediately for all subsequent tool requests. This lets you start restrictive and loosen permissions as trust builds -- for example, switching to :accept_edits after reviewing Claude's initial approach.
{:ok, session} = ClaudeCode.start_link(permission_mode: :default)
# Change mode dynamically mid-session
{:ok, _} = ClaudeCode.Session.set_permission_mode(session, :accept_edits)
# Subsequent queries use the new permission mode
session
|> ClaudeCode.stream("Now refactor the module")
|> ClaudeCode.Stream.text_content()
|> Enum.each(&IO.write/1)Mode details
Accept edits mode (:accept_edits)
Auto-approves file operations so Claude can edit code without prompting. Other tools (like Bash commands that aren't filesystem operations) still require normal permissions.
Auto-approved operations:
- File edits (Edit, Write tools)
- Filesystem commands:
mkdir,touch,rm,mv,cp
Use when: you trust Claude's edits and want faster iteration, such as during prototyping or when working in an isolated directory.
Don't ask mode (:dont_ask)
Converts any permission prompt into a denial. Tools pre-approved by :allowed_tools, settings.json allow rules, or a hook run as normal. Everything else is denied without calling can_use_tool.
Use when: you want a fixed, explicit tool surface for a headless agent and prefer a hard deny over prompting.
{:ok, result} = ClaudeCode.query(
"Summarize the project structure",
allowed_tools: ["Read", "Glob", "Grep"],
permission_mode: :dont_ask
)Bypass permissions mode (:bypass_permissions)
Auto-approves all tool uses without prompts. Hooks still execute and can block operations if needed.
Warning: Use with extreme caution. Claude has full system access in this mode. Only use in controlled environments where you trust all possible operations.
:allowed_toolsdoes not constrain this mode. Every tool is approved, not just the ones you listed. Deny rules (:disallowed_tools), explicitaskrules, and hooks are evaluated before the mode check and can still block a tool.
The Elixir SDK requires allow_dangerously_skip_permissions: true to enable this mode:
{:ok, session} = ClaudeCode.start_link(
permission_mode: :bypass_permissions,
allow_dangerously_skip_permissions: true
)For additional isolation, combine with the :sandbox option to restrict bash command execution:
{:ok, session} = ClaudeCode.start_link(
permission_mode: :bypass_permissions,
allow_dangerously_skip_permissions: true,
sandbox: %{
"environment" => "docker",
"container" => "my-sandbox"
}
)Plan mode (:plan)
Prevents tool execution entirely. Claude can analyze code and create plans but cannot make changes. Claude may use AskUserQuestion to clarify requirements before finalizing the plan. See Handle approvals and user input for handling these prompts.
Use when: you want Claude to propose changes without executing them, such as during code review or when you need to approve changes before they're made.
{:ok, result} = ClaudeCode.query(
"How should we restructure the authentication module?",
permission_mode: :plan,
system_prompt: "Analyze the codebase and propose a refactoring plan."
)Related resources
For the other steps in the permission evaluation flow:
- Handle approvals and user input -- Interactive approval prompts and clarifying questions
- Hooks -- Run custom code at key points in the agent lifecycle
- Permission rules -- Declarative allow/deny rules in settings
- Secure Deployment -- Permission rules, sandboxing, and production security
- MCP -- Connect external tools via MCP servers