AI Assistant

Permissions

The permission system controls which tools can execute and under what conditions. It combines modes, rules, classifiers, and hooks to provide layered security.

The permission system is security-critical. Changes to permission logic should be reviewed carefully, as bypasses could allow unintended file modifications, command execution, or data exfiltration.

Permission Modes

Claude Code supports several permission modes that control the baseline behavior:

const EXTERNAL_PERMISSION_MODES = [
  'acceptEdits',
  'bypassPermissions',
  'default',
  'dontAsk',
  'plan',
] as const

type InternalPermissionMode =
  | ExternalPermissionMode
  | 'auto'   // Transcript classifier-based (feature-gated)
  | 'bubble' // Propagated from parent context
defaultmode
Standard interactive mode. Prompts the user for permission on potentially dangerous operations.
acceptEditsmode
Auto-approves file edits but still prompts for other operations like shell commands.
bypassPermissionsmode
Skips all permission checks. Intended for CI/automation environments.
dontAskmode
Never prompts the user. Operations that would require approval are automatically rejected.
planmode
Read-only mode. Only allows read operations, blocking all writes and executions.
automode
Uses a transcript classifier to evaluate risk. Feature-gated behind TRANSCRIPT_CLASSIFIER.

The bypassPermissions mode disables all safety checks. Only use it in sandboxed environments where Claude Code cannot access sensitive data or systems.

Permission Rules

Rules define allow/deny/ask behavior for specific tools and content patterns:

type PermissionRuleValue = {
  toolName: string
  ruleContent?: string  // Optional content filter
}

type PermissionRule = {
  source: PermissionRuleSource
  ruleBehavior: PermissionBehavior  // 'allow' | 'deny' | 'ask'
  ruleValue: PermissionRuleValue
}

Rule Sources

Rules can originate from multiple sources, applied in a defined precedence order:

type PermissionRuleSource =
  | 'userSettings'      // ~/.claude/settings.json
  | 'projectSettings'   // .claude/settings.json
  | 'localSettings'     // .claude/settings.local.json
  | 'flagSettings'      // Feature flags
  | 'policySettings'    // Enterprise managed policies
  | 'cliArg'            // --allowedTools CLI argument
  | 'command'           // Skill-level tool restrictions
  | 'session'           // Runtime session-scoped rules

Rule Syntax

Rules use a ToolName(content pattern) syntax:

Bash(git *)        # Allow git commands in Bash
Write(src/*.ts)    # Allow writing to TypeScript files in src/
Read               # Allow all Read operations (no content filter)

The permissionRuleValueFromString() and permissionRuleValueToString() functions handle parsing and serialization.

Permission Decisions

Every tool invocation produces a PermissionDecision:

type PermissionDecision =
  | PermissionAllowDecision   // Proceed
  | PermissionAskDecision     // Prompt the user
  | PermissionDenyDecision    // Block with message
type PermissionAllowDecision = {
  behavior: 'allow'
  updatedInput?: Record<string, unknown>
  userModified?: boolean
  decisionReason?: PermissionDecisionReason
}

May include updatedInput if a hook modified the tool's input before approval.

Permission Updates

Rules can be added, replaced, or removed at runtime:

type PermissionUpdate =
  | { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'replaceRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'removeRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'setMode'; destination: PermissionUpdateDestination; mode: ExternalPermissionMode }
  | { type: 'addDirectories'; destination: PermissionUpdateDestination; directories: string[] }
  | { type: 'removeDirectories'; destination: PermissionUpdateDestination; directories: string[] }

Updates are persisted via applyPermissionUpdate() and persistPermissionUpdates().

Bash Command Classification

The bash classifier evaluates shell commands for risk. The external build (bashClassifier.ts) is a stub, but the full implementation provides:

type ClassifierResult = {
  matches: boolean
  matchedDescription?: string
  confidence: 'high' | 'medium' | 'low'
  reason: string
}

type ClassifierBehavior = 'deny' | 'ask' | 'allow'

Classifier Functions

classifyBashCommandfunction
Evaluates a command against descriptions with a specified behavior. Returns whether the command matches and a confidence level.
generateGenericDescriptionfunction
Generates a human-readable description for a command.
isClassifierPermissionsEnabledfunction
Returns whether the classifier permission system is active.
getBashPromptDenyDescriptionsfunction
Returns deny-rule descriptions for prompt-based bash classification.

Path Validation

The permission system validates file paths through additional working directories:

type AdditionalWorkingDirectory = {
  path: string
  source: WorkingDirectorySource
}

Tools that read or write files are checked against the project root and any additional directories specified in settings. Paths outside these boundaries require explicit permission.

Denial Tracking

The system tracks permission denials to detect when the model is stuck in a loop:

type DenialTrackingState = { ... }

function recordDenial(state: DenialTrackingState): void
function recordSuccess(state: DenialTrackingState): void
function shouldFallbackToPrompting(state: DenialTrackingState): boolean

After repeated denials, shouldFallbackToPrompting() returns true, which triggers a fallback to user prompting instead of automatic rejection.

Hook Integration

The permission system integrates with hooks via executePermissionRequestHooks(). PermissionRequest hooks can intercept permission checks and return allow/deny decisions programmatically, enabling automated approval workflows.

Shadowed Rule Detection

When multiple rules from different sources apply to the same tool, lower-priority rules may be "shadowed" by higher-priority ones. The deletePermissionRuleFromSettings() function handles cleanup, and shouldAllowManagedPermissionRulesOnly() enforces enterprise policy restrictions that prevent user rules from overriding managed settings.