or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

agent-definitions.mdagents.mdclient.mdconfiguration-options.mdcontent-blocks.mdcore-query-interface.mdcustom-tools.mderror-handling.mderrors.mdhook-system.mdhooks.mdindex.mdmcp-config.mdmcp-server-configuration.mdmessages-and-content.mdmessages.mdoptions.mdpermission-control.mdpermissions.mdquery.mdtransport.md
COMPLETION_SUMMARY.md

permissions.mddocs/

0

# Permission Control

1

2

Permission control provides fine-grained control over which tools Claude can execute. You can use permission modes for broad control or implement custom permission callbacks for precise per-tool decisions.

3

4

## Capabilities

5

6

### Permission Modes

7

8

Predefined permission modes for controlling tool execution.

9

10

```python { .api }

11

PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]

12

"""

13

Permission modes for tool execution.

14

15

Permission modes provide preset configurations for how Claude handles

16

tool permissions:

17

18

- 'default': Prompt user for dangerous operations (interactive mode)

19

- 'acceptEdits': Auto-accept file edits, prompt for other dangerous operations

20

- 'plan': Generate execution plan without actually executing tools

21

- 'bypassPermissions': Allow all tools without prompting (use with extreme caution)

22

23

Used in ClaudeAgentOptions.permission_mode.

24

"""

25

```

26

27

### Permission Result Types

28

29

Result types returned by permission callbacks.

30

31

```python { .api }

32

PermissionResult = PermissionResultAllow | PermissionResultDeny

33

"""

34

Union of permission result types.

35

36

Permission callbacks return either PermissionResultAllow or PermissionResultDeny

37

to indicate whether a tool should be allowed or blocked.

38

"""

39

```

40

41

### Permission Result Allow

42

43

Allow a tool to execute, optionally with modifications.

44

45

```python { .api }

46

@dataclass

47

class PermissionResultAllow:

48

"""

49

Allow permission result.

50

51

Indicates that a tool should be allowed to execute. Optionally includes

52

modifications to the tool input or permission updates.

53

54

Attributes:

55

behavior: Always "allow"

56

updated_input: Modified tool input parameters

57

updated_permissions: Permission updates to apply

58

"""

59

60

behavior: Literal["allow"] = "allow"

61

"""Behavior marker.

62

63

Always set to "allow" to indicate permission is granted.

64

"""

65

66

updated_input: dict[str, Any] | None = None

67

"""Modified tool input.

68

69

If set, the tool will be executed with these input parameters instead

70

of the original ones. Allows you to modify tool behavior on-the-fly.

71

72

Example:

73

# Original input: {"command": "rm -rf /"}

74

# Modified input: {"command": "echo 'blocked dangerous command'"}

75

updated_input={"command": "echo 'blocked dangerous command'"}

76

"""

77

78

updated_permissions: list[PermissionUpdate] | None = None

79

"""Permission updates to apply.

80

81

List of permission updates to apply after allowing this tool use.

82

Can add rules, change modes, update directories, etc.

83

84

See PermissionUpdate for details.

85

"""

86

```

87

88

### Permission Result Deny

89

90

Deny a tool execution.

91

92

```python { .api }

93

@dataclass

94

class PermissionResultDeny:

95

"""

96

Deny permission result.

97

98

Indicates that a tool should not be allowed to execute. Includes

99

a message explaining why and whether to interrupt the conversation.

100

101

Attributes:

102

behavior: Always "deny"

103

message: Explanation for denial

104

interrupt: Whether to interrupt the conversation

105

"""

106

107

behavior: Literal["deny"] = "deny"

108

"""Behavior marker.

109

110

Always set to "deny" to indicate permission is denied.

111

"""

112

113

message: str = ""

114

"""Denial message.

115

116

Human-readable explanation of why the tool was blocked. This message

117

may be shown to the user or logged.

118

119

Example:

120

"Dangerous command blocked for security reasons"

121

"Tool not available in readonly mode"

122

"""

123

124

interrupt: bool = False

125

"""Interrupt flag.

126

127

If True, the conversation will be interrupted immediately after denying

128

this tool. If False, Claude may try alternative approaches.

129

130

Use True for critical security issues, False for normal denials.

131

"""

132

```

133

134

### Permission Update

135

136

Configuration for updating permissions during execution.

137

138

```python { .api }

139

@dataclass

140

class PermissionUpdate:

141

"""

142

Permission update configuration.

143

144

Defines a change to permission settings. Can add/remove rules, change

145

modes, or modify directory access. Used in PermissionResultAllow.

146

147

Attributes:

148

type: Type of permission update

149

rules: Permission rules (for rule-based updates)

150

behavior: Permission behavior (for rule-based updates)

151

mode: Permission mode (for mode updates)

152

directories: Directories (for directory updates)

153

destination: Where to save the update

154

"""

155

156

type: Literal[

157

"addRules",

158

"replaceRules",

159

"removeRules",

160

"setMode",

161

"addDirectories",

162

"removeDirectories",

163

]

164

"""Update type.

165

166

Specifies what kind of permission change to make:

167

- 'addRules': Add new permission rules

168

- 'replaceRules': Replace existing permission rules

169

- 'removeRules': Remove permission rules

170

- 'setMode': Change the permission mode

171

- 'addDirectories': Add directories to allowed list

172

- 'removeDirectories': Remove directories from allowed list

173

"""

174

175

rules: list[PermissionRuleValue] | None = None

176

"""Permission rules.

177

178

List of rules to add, replace, or remove. Required for rule-based

179

update types (addRules, replaceRules, removeRules).

180

181

See PermissionRuleValue for rule structure.

182

"""

183

184

behavior: PermissionBehavior | None = None

185

"""Permission behavior.

186

187

Behavior to apply to rules: "allow", "deny", or "ask".

188

Required for rule-based update types.

189

"""

190

191

mode: PermissionMode | None = None

192

"""Permission mode.

193

194

The mode to set. Required for 'setMode' update type.

195

"""

196

197

directories: list[str] | None = None

198

"""Directories list.

199

200

List of directory paths. Required for directory-based update types

201

(addDirectories, removeDirectories).

202

"""

203

204

destination: PermissionUpdateDestination | None = None

205

"""Update destination.

206

207

Where to save this permission update:

208

- 'userSettings': User-level settings

209

- 'projectSettings': Project-level settings

210

- 'localSettings': Local settings

211

- 'session': Current session only

212

213

If None, applies to current session only.

214

"""

215

216

def to_dict(self) -> dict[str, Any]:

217

"""

218

Convert PermissionUpdate to dictionary format.

219

220

Converts the dataclass to a dictionary matching the TypeScript

221

control protocol format.

222

223

Returns:

224

Dictionary representation of the permission update

225

"""

226

```

227

228

### Permission Rule Value

229

230

Individual permission rule configuration.

231

232

```python { .api }

233

@dataclass

234

class PermissionRuleValue:

235

"""

236

Permission rule value.

237

238

Defines a single permission rule for a tool.

239

240

Attributes:

241

tool_name: Name of the tool this rule applies to

242

rule_content: Optional rule details or pattern

243

"""

244

245

tool_name: str

246

"""Tool name.

247

248

The name of the tool this rule applies to. Examples:

249

- "Bash"

250

- "Write"

251

- "Read"

252

- "custom_tool"

253

"""

254

255

rule_content: str | None = None

256

"""Rule content.

257

258

Optional additional details about the rule. Can be:

259

- A pattern to match against tool input

260

- A condition to evaluate

261

- Additional metadata

262

263

Format depends on the tool and use case.

264

"""

265

```

266

267

### Permission Behavior

268

269

Behavior types for permission rules.

270

271

```python { .api }

272

PermissionBehavior = Literal["allow", "deny", "ask"]

273

"""

274

Permission behaviors for rules.

275

276

- 'allow': Always allow the tool

277

- 'deny': Always deny the tool

278

- 'ask': Prompt the user for decision

279

"""

280

```

281

282

### Permission Update Destination

283

284

Where to save permission updates.

285

286

```python { .api }

287

PermissionUpdateDestination = Literal[

288

"userSettings", "projectSettings", "localSettings", "session"

289

]

290

"""

291

Permission update destinations.

292

293

Specifies where to persist permission updates:

294

- 'userSettings': Save to user-level settings (all projects)

295

- 'projectSettings': Save to project-level settings (current project)

296

- 'localSettings': Save to local settings (current directory)

297

- 'session': Apply only to current session (not persisted)

298

"""

299

```

300

301

### Tool Permission Context

302

303

Context information passed to permission callbacks.

304

305

```python { .api }

306

@dataclass

307

class ToolPermissionContext:

308

"""

309

Context information for tool permission callbacks.

310

311

Provides additional context when making permission decisions.

312

313

Attributes:

314

signal: Abort signal support (future feature)

315

suggestions: Permission suggestions from CLI

316

"""

317

318

signal: Any | None = None

319

"""Future: abort signal support.

320

321

Reserved for future use to allow canceling permission checks.

322

Currently always None.

323

"""

324

325

suggestions: list[PermissionUpdate] = field(default_factory=list)

326

"""Permission suggestions from CLI.

327

328

The CLI may provide suggested permission updates based on the

329

tool use context. Your callback can choose to apply these

330

suggestions or make its own decisions.

331

"""

332

```

333

334

### Can Use Tool Callback

335

336

Type alias for tool permission callbacks.

337

338

```python { .api }

339

CanUseTool = Callable[

340

[str, dict[str, Any], ToolPermissionContext],

341

Awaitable[PermissionResult]

342

]

343

"""

344

Tool permission callback type.

345

346

An async function that determines whether a specific tool use should

347

be allowed. Called before each tool execution.

348

349

Args:

350

tool_name: Name of the tool Claude wants to use

351

tool_input: Input parameters for the tool

352

context: Additional context including suggestions

353

354

Returns:

355

PermissionResult (either PermissionResultAllow or PermissionResultDeny)

356

357

Example:

358

async def can_use_tool(

359

tool_name: str,

360

tool_input: dict[str, Any],

361

context: ToolPermissionContext

362

) -> PermissionResult:

363

if tool_name == "Bash" and "rm" in tool_input.get("command", ""):

364

return PermissionResultDeny(

365

message="Dangerous command blocked",

366

interrupt=False

367

)

368

return PermissionResultAllow()

369

"""

370

```

371

372

## Usage Examples

373

374

### Using Permission Modes

375

376

```python

377

from claude_agent_sdk import ClaudeAgentOptions, query

378

379

# Default mode - prompt for dangerous operations

380

options = ClaudeAgentOptions(

381

permission_mode='default',

382

allowed_tools=["Read", "Write", "Bash"]

383

)

384

385

# Accept edits mode - auto-accept file changes

386

options = ClaudeAgentOptions(

387

permission_mode='acceptEdits',

388

allowed_tools=["Read", "Write", "Edit"]

389

)

390

391

# Plan mode - generate plan without execution

392

options = ClaudeAgentOptions(

393

permission_mode='plan',

394

allowed_tools=["Read", "Write", "Bash"]

395

)

396

397

# Bypass permissions - allow everything (use with caution!)

398

options = ClaudeAgentOptions(

399

permission_mode='bypassPermissions',

400

allowed_tools=["Read", "Write", "Bash"]

401

)

402

403

async for msg in query(prompt="Modify files", options=options):

404

print(msg)

405

```

406

407

### Basic Permission Callback

408

409

```python

410

from claude_agent_sdk import (

411

ClaudeAgentOptions, query, PermissionResultAllow,

412

PermissionResultDeny, ToolPermissionContext

413

)

414

415

async def can_use_tool(

416

tool_name: str,

417

tool_input: dict,

418

context: ToolPermissionContext

419

) -> PermissionResult:

420

"""Simple permission callback."""

421

# Allow Read operations

422

if tool_name == "Read":

423

return PermissionResultAllow()

424

425

# Block Bash commands

426

if tool_name == "Bash":

427

return PermissionResultDeny(

428

message="Bash commands not allowed",

429

interrupt=False

430

)

431

432

# Allow everything else

433

return PermissionResultAllow()

434

435

options = ClaudeAgentOptions(

436

allowed_tools=["Read", "Write", "Bash"],

437

can_use_tool=can_use_tool

438

)

439

440

async for msg in query(prompt="Analyze files", options=options):

441

print(msg)

442

```

443

444

### Blocking Dangerous Commands

445

446

```python

447

from claude_agent_sdk import (

448

ClaudeAgentOptions, query, PermissionResultAllow,

449

PermissionResultDeny, ToolPermissionContext

450

)

451

452

async def block_dangerous_commands(

453

tool_name: str,

454

tool_input: dict,

455

context: ToolPermissionContext

456

):

457

"""Block dangerous bash commands."""

458

if tool_name == "Bash":

459

command = tool_input.get("command", "")

460

461

dangerous_patterns = [

462

"rm -rf",

463

"dd if=",

464

"mkfs",

465

"> /dev/",

466

"chmod 777"

467

]

468

469

for pattern in dangerous_patterns:

470

if pattern in command:

471

return PermissionResultDeny(

472

message=f"Blocked dangerous command pattern: {pattern}",

473

interrupt=True # Stop immediately

474

)

475

476

return PermissionResultAllow()

477

478

options = ClaudeAgentOptions(

479

allowed_tools=["Bash"],

480

can_use_tool=block_dangerous_commands

481

)

482

483

async for msg in query(prompt="Clean up files", options=options):

484

print(msg)

485

```

486

487

### Modifying Tool Input

488

489

```python

490

from claude_agent_sdk import (

491

ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext

492

)

493

494

async def modify_bash_commands(

495

tool_name: str,

496

tool_input: dict,

497

context: ToolPermissionContext

498

):

499

"""Modify bash commands to add safety flags."""

500

if tool_name == "Bash":

501

command = tool_input.get("command", "")

502

503

# Add -i flag to rm commands for interactive prompts

504

if "rm" in command and "-i" not in command:

505

modified_command = command.replace("rm ", "rm -i ")

506

return PermissionResultAllow(

507

updated_input={"command": modified_command}

508

)

509

510

return PermissionResultAllow()

511

512

options = ClaudeAgentOptions(

513

allowed_tools=["Bash"],

514

can_use_tool=modify_bash_commands

515

)

516

517

async for msg in query(prompt="Delete old files", options=options):

518

print(msg)

519

```

520

521

### Path Restrictions

522

523

```python

524

from pathlib import Path

525

from claude_agent_sdk import (

526

ClaudeAgentOptions, query, PermissionResultAllow,

527

PermissionResultDeny, ToolPermissionContext

528

)

529

530

ALLOWED_PATHS = ["/home/user/project", "/tmp"]

531

532

async def enforce_path_restrictions(

533

tool_name: str,

534

tool_input: dict,

535

context: ToolPermissionContext

536

):

537

"""Restrict file operations to allowed paths."""

538

if tool_name in ["Read", "Write", "Edit"]:

539

file_path = tool_input.get("file_path", "")

540

abs_path = Path(file_path).resolve()

541

542

# Check if path is within allowed directories

543

allowed = any(

544

str(abs_path).startswith(str(Path(allowed_path).resolve()))

545

for allowed_path in ALLOWED_PATHS

546

)

547

548

if not allowed:

549

return PermissionResultDeny(

550

message=f"Access denied: {file_path} is outside allowed paths",

551

interrupt=False

552

)

553

554

return PermissionResultAllow()

555

556

options = ClaudeAgentOptions(

557

allowed_tools=["Read", "Write", "Edit"],

558

can_use_tool=enforce_path_restrictions

559

)

560

561

async for msg in query(prompt="Modify configuration", options=options):

562

print(msg)

563

```

564

565

### Time-Based Restrictions

566

567

```python

568

from datetime import datetime

569

from claude_agent_sdk import (

570

ClaudeAgentOptions, query, PermissionResultAllow,

571

PermissionResultDeny, ToolPermissionContext

572

)

573

574

async def business_hours_only(

575

tool_name: str,

576

tool_input: dict,

577

context: ToolPermissionContext

578

):

579

"""Only allow certain tools during business hours."""

580

hour = datetime.now().hour

581

582

# Restrict Bash during off-hours (before 9am or after 5pm)

583

if tool_name == "Bash" and (hour < 9 or hour >= 17):

584

return PermissionResultDeny(

585

message="Bash commands only allowed during business hours (9am-5pm)",

586

interrupt=False

587

)

588

589

return PermissionResultAllow()

590

591

options = ClaudeAgentOptions(

592

allowed_tools=["Bash", "Read", "Write"],

593

can_use_tool=business_hours_only

594

)

595

596

async for msg in query(prompt="Run deployment script", options=options):

597

print(msg)

598

```

599

600

### Logging All Tool Uses

601

602

```python

603

import logging

604

from claude_agent_sdk import (

605

ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext

606

)

607

608

logging.basicConfig(level=logging.INFO)

609

610

async def log_and_allow(

611

tool_name: str,

612

tool_input: dict,

613

context: ToolPermissionContext

614

):

615

"""Log all tool uses and allow them."""

616

logging.info(f"Tool use: {tool_name}")

617

logging.info(f"Input: {tool_input}")

618

619

return PermissionResultAllow()

620

621

options = ClaudeAgentOptions(

622

allowed_tools=["Read", "Write", "Bash"],

623

can_use_tool=log_and_allow

624

)

625

626

async for msg in query(prompt="Analyze project", options=options):

627

print(msg)

628

```

629

630

### User Confirmation

631

632

```python

633

from claude_agent_sdk import (

634

ClaudeAgentOptions, query, PermissionResultAllow,

635

PermissionResultDeny, ToolPermissionContext

636

)

637

638

async def ask_user_permission(

639

tool_name: str,

640

tool_input: dict,

641

context: ToolPermissionContext

642

):

643

"""Ask user for confirmation on file writes."""

644

if tool_name in ["Write", "Edit"]:

645

file_path = tool_input.get("file_path", "unknown")

646

647

print(f"\nClaude wants to modify: {file_path}")

648

print(f"Tool: {tool_name}")

649

650

# In a real application, you'd use a proper input method

651

# This is just for demonstration

652

response = input("Allow? (y/n): ")

653

654

if response.lower() != 'y':

655

return PermissionResultDeny(

656

message="User denied permission",

657

interrupt=False

658

)

659

660

return PermissionResultAllow()

661

662

options = ClaudeAgentOptions(

663

allowed_tools=["Read", "Write", "Edit"],

664

can_use_tool=ask_user_permission

665

)

666

667

async for msg in query(prompt="Fix bugs", options=options):

668

print(msg)

669

```

670

671

### Permission Updates

672

673

```python

674

from claude_agent_sdk import (

675

ClaudeAgentOptions, query, PermissionResultAllow,

676

PermissionUpdate, PermissionRuleValue, ToolPermissionContext

677

)

678

679

async def add_permission_after_use(

680

tool_name: str,

681

tool_input: dict,

682

context: ToolPermissionContext

683

):

684

"""Add permission rule after first use."""

685

if tool_name == "Read":

686

# After first read, allow all future reads automatically

687

return PermissionResultAllow(

688

updated_permissions=[

689

PermissionUpdate(

690

type="addRules",

691

rules=[PermissionRuleValue(tool_name="Read")],

692

behavior="allow",

693

destination="session" # Session only

694

)

695

]

696

)

697

698

return PermissionResultAllow()

699

700

options = ClaudeAgentOptions(

701

allowed_tools=["Read", "Write"],

702

can_use_tool=add_permission_after_use

703

)

704

705

async for msg in query(prompt="Read multiple files", options=options):

706

print(msg)

707

```

708

709

### Rate Limiting

710

711

```python

712

from datetime import datetime, timedelta

713

from claude_agent_sdk import (

714

ClaudeAgentOptions, query, PermissionResultAllow,

715

PermissionResultDeny, ToolPermissionContext

716

)

717

718

# Rate limiting state

719

tool_usage = {}

720

RATE_LIMIT = 10 # Max 10 uses per minute

721

722

async def rate_limit_tools(

723

tool_name: str,

724

tool_input: dict,

725

context: ToolPermissionContext

726

):

727

"""Rate limit tool usage."""

728

if tool_name not in tool_usage:

729

tool_usage[tool_name] = []

730

731

now = datetime.now()

732

733

# Remove old entries

734

tool_usage[tool_name] = [

735

ts for ts in tool_usage[tool_name]

736

if now - ts < timedelta(minutes=1)

737

]

738

739

# Check limit

740

if len(tool_usage[tool_name]) >= RATE_LIMIT:

741

return PermissionResultDeny(

742

message=f"Rate limit exceeded for {tool_name} ({RATE_LIMIT}/min)",

743

interrupt=False

744

)

745

746

# Record usage

747

tool_usage[tool_name].append(now)

748

749

return PermissionResultAllow()

750

751

options = ClaudeAgentOptions(

752

allowed_tools=["Bash"],

753

can_use_tool=rate_limit_tools

754

)

755

756

async for msg in query(prompt="Run many tests", options=options):

757

print(msg)

758

```

759

760

### Conditional Permissions

761

762

```python

763

from claude_agent_sdk import (

764

ClaudeAgentOptions, query, PermissionResultAllow,

765

PermissionResultDeny, ToolPermissionContext

766

)

767

768

class AppState:

769

def __init__(self):

770

self.readonly = False

771

self.trusted = False

772

773

state = AppState()

774

775

async def conditional_permissions(

776

tool_name: str,

777

tool_input: dict,

778

context: ToolPermissionContext

779

):

780

"""Apply conditional permissions based on app state."""

781

# Block writes in readonly mode

782

if state.readonly and tool_name in ["Write", "Edit", "Bash"]:

783

return PermissionResultDeny(

784

message="System is in readonly mode",

785

interrupt=False

786

)

787

788

# Allow everything if trusted

789

if state.trusted:

790

return PermissionResultAllow()

791

792

# Otherwise, apply normal rules

793

if tool_name == "Bash":

794

return PermissionResultDeny(

795

message="Bash not allowed in untrusted mode",

796

interrupt=False

797

)

798

799

return PermissionResultAllow()

800

801

options = ClaudeAgentOptions(

802

allowed_tools=["Read", "Write", "Bash"],

803

can_use_tool=conditional_permissions

804

)

805

806

# Enable readonly mode

807

state.readonly = True

808

809

async for msg in query(prompt="Analyze code", options=options):

810

print(msg)

811

```

812

813

### Using Permission Suggestions

814

815

```python

816

from claude_agent_sdk import (

817

ClaudeAgentOptions, query, PermissionResultAllow, ToolPermissionContext

818

)

819

820

async def use_suggestions(

821

tool_name: str,

822

tool_input: dict,

823

context: ToolPermissionContext

824

):

825

"""Use permission suggestions from CLI."""

826

# Check if CLI provided suggestions

827

if context.suggestions:

828

print(f"CLI suggests {len(context.suggestions)} permission updates")

829

830

# Accept suggestions

831

return PermissionResultAllow(

832

updated_permissions=context.suggestions

833

)

834

835

return PermissionResultAllow()

836

837

options = ClaudeAgentOptions(

838

allowed_tools=["Read", "Write"],

839

can_use_tool=use_suggestions

840

)

841

842

async for msg in query(prompt="Work on project", options=options):

843

print(msg)

844

```

845