How Claude Code decides what it is allowed to do
import { feature } from 'bun:bundle'import { APIUserAbortError } from '@anthropic-ai/sdk'import type { CanUseToolFn } from '../../hooks/useCanUseTool.js'import {getToolNameForPermissionCheck,
mcpInfoFromString,
} from '../../services/mcp/mcpStringUtils.js'
import type { Tool, ToolPermissionContext, ToolUseContext } from '../../Tool.js'import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'import { shouldUseSandbox } from '../../tools/BashTool/shouldUseSandbox.js'import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js'import { POWERSHELL_TOOL_NAME } from '../../tools/PowerShellTool/toolName.js'import { REPL_TOOL_NAME } from '../../tools/REPLTool/constants.js'import type { AssistantMessage } from '../../types/message.js'import { extractOutputRedirections } from '../bash/commands.js'import { logForDebugging } from '../debug.js'import { AbortError, toError } from '../errors.js'import { logError } from '../log.js'import { SandboxManager } from '../sandbox/sandbox-adapter.js'import {getSettingSourceDisplayNameLowercase,
SETTING_SOURCES,
} from '../settings/constants.js'
import { plural } from '../stringUtils.js'import { permissionModeTitle } from './PermissionMode.js'import type {PermissionAskDecision,
PermissionDecision,
PermissionDecisionReason,
PermissionDenyDecision,
PermissionResult,
} from './PermissionResult.js'
import type {PermissionBehavior,
PermissionRule,
PermissionRuleSource,
PermissionRuleValue,
} from './PermissionRule.js'
import {applyPermissionUpdate,
applyPermissionUpdates,
persistPermissionUpdates,
} from './PermissionUpdate.js'
import type {PermissionUpdate,
PermissionUpdateDestination,
} from './PermissionUpdateSchema.js'
import {permissionRuleValueFromString,
permissionRuleValueToString,
} from './permissionRuleParser.js'
import {deletePermissionRuleFromSettings,
type PermissionRuleFromEditableSettings,
shouldAllowManagedPermissionRulesOnly,
} from './permissionsLoader.js'
/* eslint-disable @typescript-eslint/no-require-imports */
const classifierDecisionModule = feature('TRANSCRIPT_CLASSIFIER') ? (require('./classifierDecision.js') as typeof import('./classifierDecision.js')): null
const autoModeStateModule = feature('TRANSCRIPT_CLASSIFIER') ? (require('./autoModeState.js') as typeof import('./autoModeState.js')): null
import {addToTurnClassifierDuration,
getTotalCacheCreationInputTokens,
getTotalCacheReadInputTokens,
getTotalInputTokens,
getTotalOutputTokens,
} from '../../bootstrap/state.js'
import { getFeatureValue_CACHED_WITH_REFRESH } from '../../services/analytics/growthbook.js'import {type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../services/analytics/index.js'
import { sanitizeToolNameForAnalytics } from '../../services/analytics/metadata.js'import {clearClassifierChecking,
Before any tool runs, canUseTool() is called in src/utils/permissions/permissions.ts. This is the gatekeeper — it returns allow, ask, or deny based on the current permission mode and rules.
The auto mode uses an ML classifier to score bash commands. If the classifier confidence is high enough, the tool runs without prompting. After enough denials, it falls back to always prompting.
Permission rules use glob patterns. A rule like bash:cat * allows cat on any file. !node_modules/** excludes a path. Rules are composed from three sources: managed (org), environment, and user settings — in that priority order.
The `bypass` mode (`--dangerously-skip-permissions`) is explicitly blocked when running as root. This is checked in `src/setup.ts` before the REPL starts.
Ask anything about Permission Model — 5-Tier Security
Powered by Groq · Enter to send, Shift+Enter for newline