Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Chapter 16: Permission System

Read the Chinese original

Positioning: This chapter analyzes Claude Code's six permission modes, three-layer rule matching mechanism, and the complete validation-permission-classification pipeline. Prerequisites: Chapter 4 (startup flow). Target audience: readers wanting to understand CC's six permission modes and three-stage permission pipeline, or developers needing to design a permission model for their own Agent.

Why This Matters

An AI Agent capable of executing arbitrary shell commands and reading/writing any file in a user's codebase — the design quality of its permission system directly determines the upper bound of user trust. Too permissive, and users face security risks — malicious prompt injection could trigger rm -rf / or steal SSH keys; too restrictive, and every operation prompts a confirmation dialog, reducing the AI coding assistant to an "automation tool that requires constant human clicking."

Claude Code's permission system attempts to find a balance between these two extremes: through six permission modes, a three-layer rule matching mechanism, and a complete validation-permission-classification pipeline, it achieves tiered control where "safe operations pass automatically, dangerous operations require manual confirmation, and ambiguous cases are adjudicated by an AI classifier."

This chapter will thoroughly dissect the design and implementation of this permission system.


16.1 Six Permission Modes

The Permission Mode is the highest-level control switch of the entire system. Users cycle through modes via Shift+Tab or specify one through the --permission-mode CLI argument. All modes are defined in types/permissions.ts:

// types/permissions.ts:16-22
export const EXTERNAL_PERMISSION_MODES = [
  'acceptEdits',
  'bypassPermissions',
  'default',
  'dontAsk',
  'plan',
] as const

Internally there are two additional non-public modes — auto and bubble — forming the complete type union:

// types/permissions.ts:28-29
export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
export type PermissionMode = InternalPermissionMode

Here is each mode's behavioral description:

ModeSymbolBehaviorTypical Scenario
default(none)All tool calls require user confirmationFirst-time use, high-security environments
acceptEdits>>File edits within the working directory pass automatically; shell commands still require confirmationDaily coding assistance
planAI can only read and search; no write operations are executedCode review, architecture planning
bypassPermissions>>Skips all permission checks (except safety checks)Batch operations in trusted environments
dontAsk>>Converts all ask decisions to deny; never prompts for confirmationAutomated CI/CD pipelines
auto>>AI classifier automatically adjudicates; internal use onlyAnthropic internal development

Each mode has a corresponding configuration object (PermissionMode.ts:42-91) containing title, abbreviation, symbol, and color key. Notably, the auto mode is registered through a feature('TRANSCRIPT_CLASSIFIER') compile-time feature gate — in external builds this code is completely removed by Bun's dead code elimination.

Mode Switching Cycle Logic

getNextPermissionMode (getNextPermissionMode.ts:34-79) defines the Shift+Tab cycle order:

External users: default → acceptEdits → plan → [bypassPermissions] → default
Internal users: default → [bypassPermissions] → [auto] → default

Internal users skip acceptEdits and plan because auto mode replaces both their functions. bypassPermissions only appears in the cycle when the isBypassPermissionsModeAvailable flag is true. The auto mode requires both a feature gate and a runtime availability check:

// getNextPermissionMode.ts:17-29
function canCycleToAuto(ctx: ToolPermissionContext): boolean {
  if (feature('TRANSCRIPT_CLASSIFIER')) {
    const gateEnabled = isAutoModeGateEnabled()
    const can = !!ctx.isAutoModeAvailable && gateEnabled
    // ...
    return can
  }
  return false
}

Side Effects of Mode Transitions

Switching modes is not just changing an enum value — transitionPermissionMode (permissionSetup.ts:597-646) handles transition side effects:

  1. Entering plan mode: Calls prepareContextForPlanMode, saving the current mode to prePlanMode
  2. Entering auto mode: Calls stripDangerousPermissionsForAutoMode, removing dangerous allow rules (detailed below)
  3. Leaving auto mode: Calls restoreDangerousPermissions, restoring stripped rules
  4. Leaving plan mode: Sets the hasExitedPlanMode state flag

16.2 Permission Rule System

Permission modes are coarse-grained switches; permission rules provide fine-grained control. A rule consists of three parts:

// types/permissions.ts:75-79
export type PermissionRule = {
  source: PermissionRuleSource
  ruleBehavior: PermissionBehavior    // 'allow' | 'deny' | 'ask'
  ruleValue: PermissionRuleValue
}

Where PermissionRuleValue specifies the target tool and an optional content qualifier:

// types/permissions.ts:67-70
export type PermissionRuleValue = {
  toolName: string
  ruleContent?: string    // e.g., "npm install", "git:*"
}

Rule Source Hierarchy

Rules have eight sources (types/permissions.ts:54-62), ranked from highest to lowest priority:

SourceLocationSharing
policySettingsEnterprise managed policyPushed to all users
projectSettings.claude/settings.jsonCommitted to git, team-shared
localSettings.claude/settings.local.jsonGitignored, local only
userSettings~/.claude/settings.jsonUser global
flagSettings--settings CLI argumentRuntime
cliArg--allowed-tools and other CLI argumentsRuntime
commandCommand-line subcommand contextRuntime
sessionIn-session temporary rulesCurrent session only

Rule String Format and Parsing

Rules are stored as strings in configuration files, formatted as ToolName or ToolName(content). Parsing is handled by permissionRuleParser.ts's permissionRuleValueFromString function (lines 93-133), which handles escaped parentheses — since rule content itself may contain parentheses (e.g., python -c "print(1)").

Special case: both Bash() and Bash(*) are treated as tool-level rules (no content qualifier), equivalent to Bash.


16.3 Three Rule Matching Modes

Shell command permission rules support three matching modes, parsed by shellRuleMatching.ts's parsePermissionRule function (lines 159-184) into a discriminated union type:

// shellRuleMatching.ts:25-38
export type ShellPermissionRule =
  | { type: 'exact'; command: string }
  | { type: 'prefix'; prefix: string }
  | { type: 'wildcard'; pattern: string }

Exact Matching

Rules without wildcards require an exact command match:

RuleMatchesDoes Not Match
npm installnpm installnpm install lodash
git statusgit statusgit status --short

Prefix Matching (Legacy :* Syntax)

Rules ending with :* use prefix matching — this is a legacy syntax for backward compatibility:

RuleMatchesDoes Not Match
npm:*npm install, npm run build, npm testnpx create-react-app
git:*git add ., git commit -m "msg"gitk

Prefix extraction is performed by permissionRuleExtractPrefix (lines 43-48): the regex /^(.+):\*$/ captures everything before :* as the prefix.

Wildcard Matching

Rules containing an unescaped * (excluding trailing :*) use wildcard matching. matchWildcardPattern (lines 90-154) converts the pattern into a regular expression:

RuleMatchesDoes Not Match
git add *git add ., git add src/main.ts, bare git addgit commit
docker build -t *docker build -t myappdocker run myapp
echo \*echo * (literal asterisk)echo hello

Wildcard matching has a carefully designed behavior: when a pattern ends with * (space plus wildcard) and the entire pattern contains only one unescaped *, the trailing space and arguments are optional. This means git * matches both git add and bare git (lines 142-145). This keeps wildcard semantics consistent with prefix rules like git:*.

The escape mechanism uses null-byte sentinel placeholders (lines 14-17) to prevent confusion between \* (literal asterisk) and * (wildcard) during regex conversion:

// shellRuleMatching.ts:14-17
const ESCAPED_STAR_PLACEHOLDER = '\x00ESCAPED_STAR\x00'
const ESCAPED_BACKSLASH_PLACEHOLDER = '\x00ESCAPED_BACKSLASH\x00'

16.4 Validation-Permission-Classification Pipeline

Interactive version: Click to view the permission decision tree animation — select different tool call scenarios (Read file / Bash rm / Edit / Write .env) and watch how requests flow through the three-stage pipeline.

When the AI model initiates a tool call, the request passes through a three-stage pipeline to determine whether it should execute. The core entry point is hasPermissionsToUseTool (permissions.ts:473), which calls the internal function hasPermissionsToUseToolInner to execute the first two stages, then handles the third stage's classifier logic in the outer layer.

flowchart TD
    START["Tool call request"] --> S1A{"Step 1a:<br/>Tool-level deny rule?"}
    S1A -- Match --> DENY["❌ deny"]
    S1A -- No match --> S1B{"Step 1b:<br/>Tool-level ask rule?"}
    S1B -- "Match (sandbox can skip)" --> ASK1["⚠️ ask"]
    S1B -- No match --> S1C{"Step 1c:<br/>tool.checkPermissions()"}
    S1C -- deny --> DENY
    S1C -- ask --> ASK1
    S1C -- Pass --> S1E{"Step 1e:<br/>Requires user interaction?"}
    S1E -- Yes --> ASK1
    S1E -- No --> S1F{"Step 1f:<br/>Content-level ask rule?<br/>(bypass-immune)"}
    S1F -- Match --> ASK1
    S1F -- No match --> S1G{"Step 1g:<br/>Safety check<br/>.git/.claude etc?<br/>(bypass-immune)"}
    S1G -- Hit --> ASK1
    S1G -- Pass --> PHASE2

    subgraph PHASE2 ["Phase Two: Mode Adjudication"]
        S2A{"Step 2a:<br/>bypassPermissions?"}
        S2A -- Yes --> ALLOW["✅ allow"]
        S2A -- No --> S2B{"Step 2b:<br/>Tool-level allow rule?"}
        S2B -- Match --> ALLOW
        S2B -- No match --> S2C{"Step 2c:<br/>Tool's own allow?"}
        S2C -- Yes --> ALLOW
        S2C -- No --> ASK2["⚠️ ask"]
    end

    ASK1 --> PHASE3
    ASK2 --> PHASE3

    subgraph PHASE3 ["Phase Three: Mode Post-Processing"]
        MODE{"Current permission mode?"}
        MODE -- dontAsk --> DENY2["❌ deny (never prompt)"]
        MODE -- auto --> CLASSIFIER["🤖 Classifier adjudication"]
        MODE -- default --> DIALOG["💬 Show permission dialog"]
        CLASSIFIER -- Safe --> ALLOW2["✅ allow"]
        CLASSIFIER -- Unsafe --> ASK3["⚠️ ask → dialog"]
    end

Phase One: Rule Validation

This is the most defensive phase; all exit paths take priority over mode adjudication. Key steps:

Steps 1a-1b (permissions.ts:1169-1206) check tool-level deny and ask rules. If Bash is denied as a whole, any Bash command is rejected. Tool-level ask rules have one exception: when sandbox is enabled and autoAllowBashIfSandboxed is on, sandboxed commands can skip the ask rule.

Step 1c (permissions.ts:1214-1223) calls the tool's own checkPermissions() method. Each tool type (Bash, FileEdit, PowerShell, etc.) implements its own permission checking logic. For example, the Bash tool parses the command, checks subcommands, and matches allow/deny rules.

Step 1f (permissions.ts:1244-1250) is a critical design: content-level ask rules (like Bash(npm publish:*)) must prompt even in bypassPermissions mode. This is because user-explicitly-configured ask rules represent clear security intent — "I want to confirm before publishing."

Step 1g (permissions.ts:1255-1258) is equally bypass-immune: write operations to .git/, .claude/, .vscode/, and shell configuration files (.bashrc, .zshrc, etc.) always require confirmation.

Phase Two: Mode Adjudication

If the tool call passed through Phase One without being denied or forced to ask, it enters mode adjudication. bypassPermissions mode allows directly at this point. In other modes, allow rules and the tool's own allow decision are checked.

Phase Three: Mode Post-Processing

This is the final gate of the permission decision pipeline. dontAsk mode converts all ask decisions to deny, suitable for non-interactive environments (permissions.ts:505-517). auto mode launches the AI classifier for adjudication — the most complex path in the entire permission system (detailed below).


16.5 isDangerousBashPermission(): Protecting the Classifier's Safety Boundary

When a user switches from another mode to auto mode, the system calls stripDangerousPermissionsForAutoMode to temporarily strip certain allow rules. Stripped rules are not deleted but saved in the strippedDangerousRules field, and restored when leaving auto mode.

The core function for determining whether a rule is "dangerous" is isDangerousBashPermission (permissionSetup.ts:94-147):

// permissionSetup.ts:94-107
export function isDangerousBashPermission(
  toolName: string,
  ruleContent: string | undefined,
): boolean {
  if (toolName !== BASH_TOOL_NAME) { return false }
  if (ruleContent === undefined || ruleContent === '') { return true }
  const content = ruleContent.trim().toLowerCase()
  if (content === '*') { return true }
  // ...check DANGEROUS_BASH_PATTERNS
}

Dangerous rule patterns include five forms:

  1. Tool-level allow: Bash (no ruleContent) or Bash(*) — allows all commands
  2. Standalone wildcard: Bash(*) — equivalent to tool-level allow
  3. Interpreter prefix: Bash(python:*) — allows arbitrary Python code execution
  4. Interpreter wildcard: Bash(python *) — same as above
  5. Interpreter with flag wildcard: Bash(python -*) — allows python -c 'arbitrary code'

Dangerous command prefixes are defined in dangerousPatterns.ts:44-80:

// dangerousPatterns.ts:44-80
export const DANGEROUS_BASH_PATTERNS: readonly string[] = [
  ...CROSS_PLATFORM_CODE_EXEC,  // python, node, ruby, perl, ssh, etc.
  'zsh', 'fish', 'eval', 'exec', 'env', 'xargs', 'sudo',
  // Additional Anthropic-internal patterns...
]

Cross-platform code execution entry points (CROSS_PLATFORM_CODE_EXEC, lines 18-42) cover all major script interpreters (python/node/ruby/perl/php/lua), package runners (npx/bunx/npm run), shells (bash/sh), and remote command execution tools (ssh).

Internal users additionally include gh, curl, wget, git, kubectl, aws, etc. — these are excluded in external builds by a process.env.USER_TYPE === 'ant' gate.

PowerShell has a corresponding isDangerousPowerShellPermission (permissionSetup.ts:157-233) that additionally detects PowerShell-specific dangerous commands: Invoke-Expression, Start-Process, Add-Type, New-Object, etc., and handles .exe suffix variants (python.exe, npm.exe).


16.6 Path Permission Validation and UNC Protection

File operation permission validation is executed by pathValidation.ts's validatePath function (lines 373-485). This is a multi-step security pipeline:

Path Validation Pipeline

Input path
  │
  ├─ 1. Strip quotes, expand ~ ──→ cleanPath
  ├─ 2. UNC path detection ──→ Reject if matched
  ├─ 3. Dangerous tilde variant detection (~root, ~+, ~-) ──→ Reject if matched
  ├─ 4. Shell expansion syntax detection ($VAR, %VAR%) ──→ Reject if matched
  ├─ 5. Glob pattern detection ──→ Reject for writes; validate base directory for reads
  ├─ 6. Resolve to absolute path + symlink resolution
  └─ 7. isPathAllowed() multi-step check

UNC Path NTLM Leak Protection

On Windows, when an application accesses a UNC path (e.g., \\attacker-server\share\file), the operating system automatically sends NTLM authentication credentials for authentication. Attackers can exploit this mechanism: through prompt injection, they can make the AI read or write to a UNC path pointing to a malicious server, thereby stealing the user's NTLM hash.

containsVulnerableUncPath (shell/readOnlyCommandValidation.ts:1562) detects three UNC path variants:

// readOnlyCommandValidation.ts:1562-1596
export function containsVulnerableUncPath(pathOrCommand: string): boolean {
  if (getPlatform() !== 'windows') { return false }

  // 1. Backslash UNC: \\server\share
  const backslashUncPattern = /\\\\[^\s\\/]+(?:@(?:\d+|ssl))?(?:[\\/]|$|\s)/i

  // 2. Forward-slash UNC: //server/share (excluding :// in URLs)
  const forwardSlashUncPattern = /(?<!:)\/\/[^\s\\/]+(?:@(?:\d+|ssl))?(?:[\\/]|$|\s)/i

  // 3. Mixed separators: /\\server (Cygwin/bash environments)
  // ...
}

Note the second regex uses a (?<!:) negative lookbehind to exclude URLs like https:// — a legitimate double-slash use case. The hostname pattern [^\s\\/]+ uses an exclusion set rather than a character whitelist, to catch Unicode homoglyph attacks (e.g., substituting Cyrillic 'а' for Latin 'a').

TOCTOU Protection

Path validation also defends against multiple TOCTOU (Time-of-Check-to-Time-of-Use) attacks:

  • Dangerous tilde variants (lines 401-411): ~root is resolved as a relative path to /cwd/~root/... during validation, but Shell expands it to /var/root/... at execution time
  • Shell variable expansion (lines 423-436): $HOME/.ssh/id_rsa is a literal string during validation, but Shell expands it to the actual path at execution time
  • Zsh equals expansion (same): =rg expands to /usr/bin/rg in Zsh

All these cases are defended by rejecting paths containing specific characters ($, %, =), requiring manual user confirmation.

isPathAllowed() Multi-Step Check

After path sanitization, isPathAllowed (pathValidation.ts:141-263) performs the final permission adjudication:

  1. Deny rules take priority: Any matching deny rule immediately rejects
  2. Internally editable paths: Plan files, scratchpads, agent memory, and other internal paths under ~/.claude/ are automatically allowed for editing
  3. Safety checks: Write operations to dangerous directories (.git/, .claude/) and shell configuration files are flagged for confirmation
  4. Working directory check: When the path is within the allowed working directory, read operations pass automatically; write operations require acceptEdits mode
  5. Sandbox write whitelist: When sandbox is enabled, its configured writable directories are automatically allowed
  6. Allow rules: Matching allow rules grant permission

16.7 Auto Mode's Classifier Pipeline

When the permission mode is auto and a tool call reaches Phase Three's ask decision, the system launches the YOLO classifier (yoloClassifier.ts) for AI adjudication. The classifier's design philosophy is: in most cases, AI can determine whether an operation is safe.

Safe Tool Whitelist

Not all tools need to go through the classifier. classifierDecision.ts defines a safety whitelist (SAFE_YOLO_ALLOWLISTED_TOOLS, lines 56-94), containing read-only operations (FileRead, Grep, Glob), metadata operations (Task-related tools), and UI tools (EnterPlanMode, AskUserQuestion). Whitelisted tools are directly allowed.

acceptEdits Fast Path

Before calling the classifier, the system simulates an acceptEdits mode check (permissions.ts:600-648): if the tool would be allowed in acceptEdits mode (e.g., file edits within the working directory), the classifier is skipped and permission is granted directly. This avoids unnecessary API calls. However, Agent (sub-agent) and REPL (code execution) tools are explicitly excluded — their safety cannot be determined by path alone.

Consecutive Denial Tracking

denialTracking.ts implements a consecutive denial counter. When the classifier denies operations multiple consecutive times, the system degrades to manual prompting mode — a safety net for potential classifier misjudgments. Each successful tool call resets the counter.

The Classifier's Two-Stage Architecture

The YOLO classifier (yoloClassifier.ts) uses a two-stage decision process:

  1. Stage 1 (Fast): Low-latency model makes a quick judgment
  2. Stage 2 (Thinking): When Stage 1 is uncertain, a more powerful model performs deeper analysis

The classifier receives the current session context (tool call history, user messages) and the command awaiting adjudication, returning a shouldBlock boolean and a reason description. The result structure contains detailed telemetry fields (token usage, latency, request IDs) for subsequent analysis.


16.8 Dangerous File and Directory Protection

filesystem.ts defines two categories of protected objects:

// filesystem.ts:57-79
export const DANGEROUS_FILES = [
  '.gitconfig', '.gitmodules',
  '.bashrc', '.bash_profile', '.zshrc', '.zprofile', '.profile',
  '.ripgreprc', '.mcp.json', '.claude.json',
] as const

export const DANGEROUS_DIRECTORIES = [
  '.git', '.vscode', '.idea', '.claude',
] as const

These files and directories can be used for code execution or data exfiltration:

  • .gitconfig can configure core.sshCommand to execute arbitrary code
  • .bashrc/.zshrc execute automatically on every Shell startup
  • .vscode/settings.json can configure tasks and auto-run in terminals

Write operations to these paths are flagged as safetyCheck type in checkPathSafetyForAutoEdit, with bypass immunity — user confirmation is required even in bypassPermissions mode. However, in auto mode, some safety checks (such as sensitive file paths) are marked as classifierApprovable: true, allowing the classifier to automatically approve them when context is sufficient.

Dangerous Removal Path Detection

isDangerousRemovalPath (pathValidation.ts:331-367) prevents deletion of root directories, home directories, Windows drive roots, and their direct children (/usr, /tmp, C:\Windows). It also handles path separator normalization — in Windows environments both C:\\Windows and C:/Windows are correctly identified.


16.9 Shadowed Rule Detection

When users configure contradictory permission rules — for example, denying Bash in project settings but allowing Bash(git:*) in local settings — the allow rule will never take effect. shadowedRuleDetection.ts's UnreachableRule type (lines 19-25) records such cases:

export type UnreachableRule = {
  rule: PermissionRule
  reason: string
  shadowedBy: PermissionRule
  shadowType: ShadowType       // 'ask' | 'deny'
  fix: string
}

The system detects and alerts users about which allow rules are shadowed by higher-priority deny/ask rules, and how to fix them.


16.10 Permission Update Persistence

Permission updates are described via the PermissionUpdate union type (types/permissions.ts:98-131), supporting six operations: addRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories. Each operation specifies a target storage location (PermissionUpdateDestination).

When a user selects "Always allow" in the permission dialog, the system generates an addRules update, typically targeting localSettings (local settings, not committed to git). The shell tool's suggestion generation function (shellRuleMatching.ts:189-228) generates exact match or prefix match suggestions based on command characteristics.


16.11 Design Reflections

Claude Code's permission system demonstrates several noteworthy design principles:

Defense in depth. Deny rules intercept at the front of the pipeline, safety checks have bypass immunity, and auto mode strips dangerous rules upon entry — multiple layers of protection ensure that a single point of failure doesn't create a security gap.

Safety intent is non-overridable. User-explicitly-configured ask rules (Step 1f) and system safety checks (Step 1g) are not affected by bypassPermissions mode. This design acknowledges the value of bypass mode (batch operation efficiency) while protecting the safety boundaries that users intentionally set.

TOCTOU consistency. The path validation system rejects all path patterns that could produce semantic differences between "validation time" and "execution time" (shell variables, tilde variants, Zsh equals expansion), rather than trying to parse them correctly — choosing a safe, conservative strategy over a "clever" compatibility one.

Classifier as safety net, not replacement. The auto mode classifier is not a replacement for permission checks but a supplementary layer after rule validation. It only handles the gray areas where "rules don't have a clear answer," with a consecutive denial degradation mechanism to prevent system runaway.

These principles together form a permission architecture that balances security and usability — neither losing the AI Agent's value through excessive conservatism, nor exposing users to risk through excessive trust.


What Users Can Do

Permission Mode Selection Recommendations

  • Daily development: Use acceptEdits mode — file edits pass automatically, shell commands still require confirmation, the best balance of security and efficiency
  • Code review/architecture exploration: Use plan mode — AI can only read and search, eliminating accidental modifications
  • Batch automation tasks: Use bypassPermissions mode — but note that safety checks (write operations to .git/, .bashrc, etc.) still require confirmation

Rule Configuration Tips

  • Use .claude/settings.json (project-level) to define team-shared allow/deny rules, committed to git
  • Use .claude/settings.local.json (local-level) to define personal preference rules, automatically gitignored
  • Use wildcard syntax to simplify rules: Bash(git *) allows all git subcommands
  • If allow rules don't take effect after configuring deny rules, check for rule shadowing — the system will indicate shadowed rules and suggest fixes

Security Considerations

  • Even with bypassPermissions enabled, write operations to dangerous files like .gitconfig, .bashrc, .zshrc still require confirmation — this is intentional security design
  • When using auto mode, the system automatically strips dangerous Bash allow rules (like Bash(python:*)); they are restored when leaving auto mode
  • Shift+Tab can switch between modes at any time

Version Evolution: v2.1.91 Changes

The following analysis is based on v2.1.91 bundle signal comparison, combined with v2.1.88 source code inference.

Auto Mode Formalization

In v2.1.88, auto mode already existed in internal code (resetAutoModeOptInForDefaultOffer.ts, spawnMultiAgent.ts:227) but did not appear in sdk-tools.d.ts's public API definition. v2.1.91 formally includes it:

- mode?: "acceptEdits" | "bypassPermissions" | "default" | "dontAsk" | "plan";
+ mode?: "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";

This means SDK users can now explicitly request auto mode through the public API — i.e., automatic permission approval driven by the TRANSCRIPT_CLASSIFIER.

Bash Security Pipeline Simplification

v2.1.91 removes all infrastructure related to the tree-sitter WASM AST parser:

Removed SignalOriginal Purpose
tengu_tree_sitter_loadWASM module load tracking
tengu_tree_sitter_security_divergenceAST vs regex parsing divergence detection
tengu_tree_sitter_shadowShadow mode parallel testing
tengu_bash_security_check_triggered23 security check triggers
CLAUDE_CODE_DISABLE_COMMAND_INJECTION_CHECKInjection check disable switch

Removal reason: v2.1.88 source code comment CC-643 documented a performance issue — complex compound commands triggered splitCommand producing exponential subcommand arrays, each executing tree-sitter parsing + ~20 validators + logEvent, causing microtask chain starvation of the event loop and triggering REPL 100% CPU freezes.

v2.1.91 reverts to a pure JavaScript regex/shell-quote scheme. The treeSitterAnalysis.ts (507-line AST-level analysis) described in Section 16.x of this chapter applies only to v2.1.88.