300 documented limitations of Claude Code's hook system. Collected from GitHub issues and testing. Source. JSON. Feed.
When a user types @.env in the prompt, Claude Code injects the file content directly into the conversation. No tool call happens, so PreToolUse hooks never fire. A file-guard rule for .env blocks Read .env and Edit .env but cannot block @.env. This is a known gap in the hook system. Workaround: use managed-settings.json denyRead patterns alongside hooks for defense in depth.
On Windows, Claude Code routes all hook commands through `/usr/bin/bash` even when a different shell is configured. Bash-based hooks work if Git Bash is installed (it provides /usr/bin/bash). All 7 Boucle hooks now ship native PowerShell equivalents (.ps1) that bypass this limitation. Use pwsh -File path/to/hook.ps1 in your hook command to run them directly. See install.ps1 for one-line setup.
PreToolUse hooks fire correctly for MCP server tools, but `permissionDecision: "deny"` is silently ignored -- the MCP tool call proceeds anyway. This means hooks cannot block MCP tools. This is a platform bug, not an enforce-hooks limitation. Workaround: block the MCP server name in managed-settings.json disallowedTools instead.
Claude Code supports three hook types: command, agent, and prompt. Only command actually blocks execution. Agent and prompt hooks fire but do not prevent the tool call and cannot deliver feedback to the model. enforce-hooks generates command-type hooks exclusively. If you write custom hooks, use "type": "command" for any hook that needs to enforce rules.
If your .claude/settings.json contains invalid JSONC (e.g., commented-out JSON blocks), Claude Code silently falls back to default settings with no hooks or rules loaded. If your hooks suddenly stop firing, check your settings.json syntax first.
When running Claude Code with -p (pipe/print mode), no hooks execute at all: PreToolUse, PostToolUse, and PermissionRequest are all silently skipped (#40506). The --bare flag goes further, also skipping LSP, plugin sync, and skill directory walks for faster scripted startup. This affects autonomous agent loops, CI pipelines, and any workflow using claude -p or claude --bare -p for headless execution. The model executes tools with no hook enforcement regardless of what is configured in settings.json. **CAUTION:** Some community workarounds for permission bugs (e.g., #40502 recommends "use hooks for safety with bypassPermissions") are invalid in -p mode because the hooks they depend on never fire. If you run an autonomous loop via claude -p, hooks provide zero protection. Use OS-level controls (file permissions, network policy, containerization) as the enforcement layer instead.
When --dangerously-skip-permissions is enabled, PreToolUse hooks can cause the permission state to reset mid-session, reverting all tools to manual approval after 30 minutes to 2 hours. Disabling hooks is the only workaround. If you use hooks in autonomous mode and find tools suddenly requiring approval, this platform bug is the likely cause.
Hooks configured with "type": "prompt" return a 400 error on Vertex AI backends ("output_config: Extra inputs"). enforce-hooks only generates command-type hooks so this does not affect it directly, but custom prompt hooks will silently fail on Vertex.
Agents spawned via the Agent tool do not consistently inherit permission settings from the parent session. Hooks configured at the project level should still fire for subagents (they share the same .claude/settings.json), but global permission preferences may not propagate. Verify hook behavior in subagent workflows.
File paths under ~/.claude/projects/*/memory/ auto-bypass Edit/Write approval with no opt-out. Claude can modify memory files without the user seeing a prompt. A PreToolUse hook returning block for writes to memory paths still works, but you must set it up explicitly. Add memory paths to your file-guard config or enforce-hooks rules if you want protection.
Claude Code's built-in skills perform Write/Edit internally through the Skill tool wrapper. PreToolUse hooks fire on the Skill tool invocation, not on the individual file operations inside it. A hook checking "is this write targeting .env?" won't fire because the tool name is Skill, not Write. There is no workaround for this yet. See #38040.
Hooks that track session state (e.g., "which files has Claude read?") break across context compaction boundaries. After compaction, Claude's context no longer contains previously-read files, but hook state still shows them as "recently read." This can cause false gates (blocking a re-read Claude needs) or false passes (allowing an action the hook thinks Claude is informed about). MITIGATED: PostCompact hook event now exists in Claude Code. Stateful hooks can register a PostCompact handler to clear their caches when compaction occurs. See read-once's compact.sh for an example implementation.
Hooks configured with "async": true receive zero bytes on stdin on macOS (works on Linux). Synchronous hooks work correctly on both platforms. enforce-hooks generates synchronous command hooks, so this does not affect it. If you add custom async hooks on macOS, remove the "async": true flag as a workaround.
When Claude Code is launched from a git hook (post-commit, pre-push, etc.), it inherits the GIT_INDEX_FILE environment variable. Plugin initialization then writes plugin file entries into the project's git index, silently corrupting it. Workaround: unset GIT_INDEX_FILE before invoking Claude from any git hook. This is a platform bug, not an enforce-hooks issue.
Hooks with "type": "prompt" send an LLM call per invocation, adding token costs that are not documented in the billing docs. enforce-hooks generates only "type": "command" hooks, which run as local processes with zero API cost. If you need reasoning-based enforcement, be aware that prompt hooks double your per-response cost.
If a hook returns {"permissionDecision": "ask"} (intending to let the user decide), the session permanently loses bypass mode after the user responds to the prompt. The permission state machine does not restore the previous mode. All subsequent tool calls revert to manual approval for the rest of the session. Do not use permissionDecision: "ask" in any hook if you run with --dangerously-skip-permissions or bypassPermissions. Use "decision": "block" with a clear reason instead.
When Claude uses the Agent tool with isolation: "worktree" or the in-session EnterWorktree tool, configured worktree hooks do not execute. Hooks that guard worktree creation or cleanup only fire for CLI-level worktree operations, not for mid-session agent-spawned worktrees. There is no workaround. If you use worktree-guard, be aware it protects ExitWorktree from the tool but not from internal session management.
After a background Agent with isolation: "worktree" completes, the parent session's working directory can silently drift to the worktree path. Subsequent commands execute in the wrong directory without warning. No hook can detect this because the CWD change happens outside the tool-call lifecycle. Verify your working directory (pwd) after background worktree agents complete.
If a hook script exits with code 2, Claude Code treats it as a crash (closed as intended behavior). For Bash tool calls, crashed hooks still block. For Edit and Write tools, crashed hooks are silently ignored and the operation proceeds. enforce-hooks generates hooks that always exit 0, so this does not affect generated hooks. But custom hook scripts that use exit 2 on the deny path will appear to work in Bash tests and silently fail on Edit/Write. Always use exit 0 with {"decision":"block","reason":"..."} JSON on stdout.
PreToolUse hooks can return updatedInput to rewrite tool inputs before execution. For most tools this works, but for the Agent tool, the rewritten input is silently discarded and the original prompt is used. Hooks that sanitize or modify subagent prompts will appear to succeed (exit 0, JSON accepted) but have no effect. There is no workaround. Use "decision": "block" to reject unsafe Agent prompts instead of trying to rewrite them.
If the project path contains spaces (e.g., /Users/name/My Projects/app/), hook scripts fail with parse errors because the path is passed unquoted in some internal contexts. All enforce-hooks generated hooks and Boucle-framework hooks quote their paths, but the platform itself may break path delivery. Workaround: avoid spaces in project directory paths.
When Claude Code is launched with both --worktree and --tmux, it uses a separate codepath that creates git worktrees directly, bypassing WorktreeCreate and WorktreeRemove hooks. Any hooks guarding worktree creation or cleanup will not fire in this mode. Workaround: use --worktree without --tmux.
Plugins set to false in enabledPlugins still have their hooks executed by Claude Code. Stop hooks, PreToolUse hooks, and other plugin-registered hooks fire even when the plugin is explicitly disabled. There is no workaround other than removing the plugin entirely.
All tool-level hooks (PreToolUse, PostToolUse) operate after file contents have already entered the conversation context. A Read tool call returns file contents into the model's context, and PostToolUse cannot modify tool output, only block. This means secrets in read files (API keys, credentials, PII) are sent to the API provider regardless of PostToolUse hooks. PreToolUse can prevent the Read from happening (file-guard does this), but once a file is read, its contents are in the API payload. For full exfiltration prevention, a PreApiCall hook or local proxy (ANTHROPIC_BASE_URL) is needed. See #39882 for the feature request.
The Agent tool's isolation: "worktree" option can silently run the agent in the main repository instead of creating an isolated worktree. The result metadata shows worktreePath: done and worktreeBranch: undefined. No hook can detect this because the worktree was never created. Combined with #36205 (EnterWorktree ignores hooks) and #38448 (CWD drift), worktree isolation has multiple failure modes that hooks cannot address.
After a worktree is merged and deleted, stop hooks fail with ENOENT because the session's CWD no longer exists. Node.js reports the error as /bin/sh not found rather than the missing CWD. Any cleanup hooks registered for the session will not run.
When Claude Code launches from a linked git worktree, it uses git rev-parse --git-common-dir to derive the project path, which resolves to the main worktree's directory. Both worktrees share the same memory and CLAUDE.md files, causing cross-contamination of project-specific rules. Hooks fire correctly in either worktree, but any @enforced rules loaded from the wrong CLAUDE.md may not match the project context. There is no workaround at the hook level; this requires a platform fix.
Claude Code's built-in bash permission system misparses `\;` in find -exec as a command separator, classifying the redirect suffix (e.g., 2 from 2>/dev/null) as a standalone command. This does not affect hooks (bash-guard receives the full command string and parses it correctly), but it causes confusing permission prompts for safe find commands. If users report permission prompts for 2 as a command, this is the platform bug.
When a Claude Code plugin is updated through the marketplace, the update process strips the execute bit from .sh files. Hook scripts that were chmod +x after install silently become non-executable, and Claude Code skips them without warning. This affects any bash-based hook delivered through the marketplace. Workaround: re-run chmod +x on your hook scripts after marketplace updates, or use safety-check to detect non-executable hooks.
When a Stop hook returns {"decision": "block"} to prevent an action, Claude Code displays "Hook Error" in the transcript instead of showing the block reason. The model reads this label and may abandon the task prematurely, thinking a system error occurred rather than a deliberate enforcement. This is the same underlying issue as the exit code 3 proposal, which would let hooks signal intentional blocks distinctly from errors. No workaround. Use PreToolUse hooks for enforcement where possible, since they show block reasons correctly.
The PostToolUse event for ExitPlanMode does not fire when a user accepts a plan with "clear context." Hooks that track plan completion or trigger actions after plan acceptance will miss this transition. There is no workaround.
There is no way to unit-test hook configurations without actually triggering tool calls. Iterating on hook logic requires live sessions with real tool invocations. Affects anyone developing or debugging custom hooks.
When plugins are synced via the marketplace, hook files are downloaded as 644 (non-executable). Any .sh hooks delivered via marketplace plugins need manual chmod +x after every sync. Same root cause as #39954.
When exiting plan mode, the permission state resets to acceptEdits instead of restoring the previous mode (e.g., bypassPermissions). Workflows that enter plan mode then resume with elevated permissions will find permissions unexpectedly downgraded.
Path deny rules in .claude/settings.json only restrict Claude Code's built-in file tools (Read, Write, Edit, Glob, Grep). The Bash tool executes commands as the user's OS process with no path checking against deny rules. Claude can cat, grep, or head files in denied directories via shell commands, silently bypassing the restriction. Users relying on path deny for security have a false sense of protection. Workaround: use a PreToolUse hook on the Bash tool that checks command strings against denied paths. bash-guard does this among other protections. For defense in depth, also use OS-level file permissions to deny the user account access.
Permission rules that ask or deny specific commands (e.g., Bash(rm *)) can be silently bypassed by prepending `cd .. &&` to the command string. The permission matcher checks the full command string against the rule pattern; adding a cd prefix changes the string enough to avoid the match. This is distinct from the path-deny bypass (#39987) — here the command itself is the same, but the cd prefix defeats pattern matching. Workaround: use PreToolUse hooks that parse the compound command and check each segment independently. bash-guard handles &&-chained commands. See #37621.
When Claude spawns subagents via the Agent tool, the parent treats subagent summaries as ground truth without checking claims against actual tool output. Subagents can report inflated counts, phantom operations, or partial searches as exhaustive, and the parent relays these to the user. No hook can intercept the Agent tool's return value or validate subagent claims. This is an architecture-level gap, not a hook limitation.
The companyAnnouncements field in .claude/settings.json is intended for enterprise managed settings, but project-level settings can set it too. A malicious repository can include .claude/settings.json with fake company messages that appear identical to legitimate enterprise announcements. This is a social engineering vector: the messages display as "Message from [COMPANY]" with no indication they originate from the repo, not the organization. No hook can intercept settings loading. Workaround: safety-check detects companyAnnouncements in project-level settings and warns. Always verify .claude/settings.json when cloning unfamiliar repos.
In SessionEnd hook configurations, hooks with "type": "agent" are silently skipped while "type": "command" hooks in the same block fire correctly. The event itself fires (command hooks prove this), but agent hooks are filtered out during execution. Agent-type hooks work in other events like Stop. No workaround for session-end cleanup that requires agent capabilities.
When using the Claude Agent SDK with --resume and --json-schema, the CLI's built-in StructuredOutput stop hook enforcement only fires once per session. On resumed sessions, the internal "already called" flag persists and enforcement is silently skipped, returning structured_output: null. Workaround: implement your own Stop hook callback that returns {"decision": "block"} when structured_output is missing on resumed sessions. Note that continue: false terminates the session instead of giving Claude another turn.
The hook runner executes hooks from installed-but-not-enabled marketplace plugins. Plugins that exist in ~/.claude/plugins/marketplaces/ but are not listed in enabledPlugins still have their SessionStart hooks loaded and executed. This means non-enabled code runs on every session start without user consent. Related to #39307 (disabled plugins run hooks). No workaround short of manually deleting unwanted plugin directories.
Setting "permission-mode": "bypassPermissions" in .claude/settings.local.json is silently ignored. The only working method to enable bypass mode is the CLI flag --dangerously-skip-permissions. Similarly, "skipDangerousModePermissionPrompt": true only suppresses the startup warning without actually enabling bypass, and "dangerouslySkipPermissions": true under "permissions" is also ignored. Automated workflows that configure bypass mode via settings files will still see permission prompts.
Stop hooks configured in .claude/settings.json do not execute when Claude Code runs inside the VSCode extension. The same hooks fire correctly in CLI sessions. Other hook types (PreToolUse, PostToolUse, SessionStart) all work in VSCode. This is a platform gap, not a configuration error. If you rely on Stop hooks for session-end enforcement or cleanup, those protections are silently absent in VSCode. No workaround. Similar to the earlier Notification hooks VSCode gap.
The /plugin install flow does not distinguish between inert skills (markdown prompt files) and plugins that include hooks or scripts. A plugin can ship a SessionStart hook that runs arbitrary commands on every future session with no disclosure, no consent prompt, and no visual indicator that executable components were installed. Combined with auto-update (enabled by default for official marketplace), a previously-safe plugin could gain hooks in a later version. The install UI shows no permissions manifest. Workaround: after installing any marketplace plugin, inspect ~/.claude/plugins/marketplaces/ for hooks.json files and review their contents. safety-check detects plugin-installed hooks and warns.
On Windows, usernames like "Lea Chan" create home directories with spaces (e.g., C:\Users\Lea Chan\). Hook commands that reference $HOME or ${CLAUDE_PLUGIN_ROOT} get word-split by bash at the space, producing bash: /c/Users/Lea: No such file or directory. This affects ALL hooks, not just enforce-hooks. The root cause is in Claude Code's hook runner, which does not properly quote expanded paths before passing them to bash -c. Workarounds: create a symlink from a space-free path to your home directory and update hook command paths, or use PowerShell hooks (.ps1) on Windows which handle spaces natively. safety-check detects spaces in $HOME and warns. Related to #39478 (spaces in working directory).
Plugin hooks (e.g., stop-hook.sh) lose their execute bit when cached by the marketplace plugin system. Same root cause as #39954 (marketplace strips +x) and #39964 (sync strips +x), but the trigger is the caching layer rather than explicit update or sync. Stop hooks are particularly affected because they are only invoked at session end, so the permission loss goes unnoticed until a critical moment. Workaround: re-run chmod +x on plugin hook scripts after any marketplace operation, or use safety-check to detect non-executable hooks.
The agent_id and agent_type fields are only available in SubagentStart/SubagentStop hook events. They are absent from PreToolUse and PostToolUse input. A hook cannot tell whether a tool call originates from the main conversation or a subagent. This means per-agent policies (e.g., "only subagents may Edit files") are impossible to enforce. No workaround at the hook level; this requires a platform change to add agent_id to the common hook input schema.
The platform's ExitWorktree tool checks unmerged commits using SHA comparison (git log main..branch). After a squash merge, the original SHAs are not on main (the squash creates a new SHA), so ExitWorktree falsely warns about unmerged commits. worktree-guard solves this by using git cherry for content-equivalent detection instead of SHA comparison. But the platform's own ExitWorktree warning (separate from our hook) still shows the false positive.
Claude Code's runtime silently deletes `.kiro/` directories between tool calls, regardless of .gitignore status. The deletion is name-specific (renaming to .sd/ avoids it) and happens outside the hook lifecycle. No PreToolUse or PostToolUse event fires for this. File-guard cannot protect directories that the runtime itself removes. If you need persistent project directories, avoid names that conflict with IDE integrations (.kiro, .cursor, etc.).
The plugin system's marketplace auto-update mechanism deletes the marketplace directory before re-cloning. If the re-clone fails (network timeout, rate limit, disk full), the directory stays deleted and all plugins installed from that marketplace break. This includes any hooks those plugins shipped. The deletion happens outside the hook lifecycle, so no hook can prevent or detect it. Workaround: back up ~/.claude/plugins/marketplaces/ before relying on marketplace plugins for critical hooks. Consider vendoring important plugin hooks into your project's .claude/settings.json instead of depending on marketplace delivery.
In multi-agent setups using TeamCreate and SendMessage, teammate summaries can appear as `Human:` turns in the conversation. The orchestrator agent treats these phantom messages as legitimate user input and acts on them. No hook can intercept this because it happens in conversation turn management, not in tool calls. This is a trust boundary violation in long sessions with frequent context compression and 529 (overloaded) errors. Workaround: use canary-word protocols to detect phantom messages, and verify unexpected "user" messages before acting on them.
The Agent tool's isolation: "worktree" option falsely reports "not in a git repository" on Windows 11 when using Git Bash. The spawned subprocess resolves the working directory differently (POSIX vs Windows paths), causing the git repo check to fail. The agent falls back to running without isolation. Related to #39886 (worktree isolation silently fails). No workaround at the hook level. Windows users relying on worktree isolation for agent sandboxing get no isolation.
The security-guidance marketplace plugin (and potentially others) hardcodes `python3` in its hook command. On Windows, python3 does not exist as a command (Python installs as python or py). Every Edit, Write, and MultiEdit operation fails with a hook error. This is a plugin authoring bug, not a platform bug, but it affects any Windows user who installs marketplace plugins with Python-based hooks. Workaround: edit the plugin's hook command in ~/.claude/plugins/marketplaces/ to use python or py instead. Our hooks avoid this by shipping native PowerShell (.ps1) equivalents for Windows.
The allow and deny rules in settings.json use case-sensitive string matching for file paths, even on Windows (NTFS) where the filesystem is case-insensitive. A rule allowing Edit(C:\Users\alice\project\*) will not match C:\Users\Alice\Project\file.txt. This creates silent permission bypass on Windows: the model may access paths that visually match a deny rule but differ in casing. No workaround at the hook level. safety-check detects Windows and warns about this. Related to #40084 (spaces in paths) and #40172 (hardcoded python3).
The sandbox.network.allowedDomains setting only intercepts HTTPS traffic via the CONNECT tunnel. Plain HTTP requests (e.g., curl http://unauthorized-domain.com) pass through unfiltered because the proxy sees the Host header but does not enforce domain rules on non-CONNECT requests. This is a security gap: prompt injection payloads can exfiltrate data over plain HTTP even when allowedDomains is configured. The SOCKS proxy correctly blocks both HTTP and HTTPS. Workaround: use bash-guard to detect outbound HTTP requests to non-allowed domains, or configure OS-level firewall rules. safety-check warns when allowedDomains is configured.
Claude Code's auto-memory system appends new entries to the bottom of MEMORY.md, but truncates from the bottom after 200 lines. This means as memory grows, the most recently learned information is lost first while stale entries persist. Not hookable — this is internal to the memory subsystem. Affects any long-running agent relying on built-in memory. Workaround: manage your own memory file (like HOT.md) and structure it with newest-first ordering, or periodically consolidate the memory index.
After successful connection and handshake, Claude Code terminates all stdio-based MCP servers simultaneously with no preceding error. The timeout interval shrinks over the session lifetime (60s → 30s → 10s). Cloud-hosted MCPs are unaffected (different transport). The only recovery is manual /mcp reconnection, which itself gets killed again. Not hookable — the kill signal originates from the runtime, not from a tool call. Affects anyone running local MCP servers.
When a user sets their model to Opus via /model, the Agent tool can still spawn subagents with cheaper models by passing model: "sonnet" or model: "haiku". The user sees no indication that work was delegated to a different model. Not hookable — the SubagentStart event does not include the model parameter, and PreToolUse for the Agent tool fires before the model is resolved. CLAUDE.md rules like "use Opus exclusively" are ignored for subagent spawning.
When a PreToolUse or UserPromptSubmit hook returns additionalContext, the injected text is appended permanently to the conversation instead of being treated as ephemeral. Each tool call adds another copy, causing the context to grow unboundedly and waste tokens. Affects hook authors who use additionalContext for tips, warnings, or contextual guidance — the guidance is correct the first time but pollutes the conversation on subsequent calls. No workaround at the hook level. Keep additionalContext short and consider rate-limiting how often your hook returns it.
When the parent session runs with --dangerously-skip-permissions, subagents spawned via the Agent tool still prompt on every Edit/Write call. Fourteen edits across eight files produced fourteen manual prompts. The bypass flag only applies to the parent session's permission state. A PreToolUse hook returning {"allow": true} would suppress the prompts, but it applies globally to all users of that hook configuration, not just bypass-mode sessions. Hooks cannot inspect the parent session's permission mode to conditionally allow. This compounds with #37730 (subagents don't inherit permission settings) and #40211 (model override without consent). Autonomous pipelines that spawn subagents for write-heavy tasks will stall on permission prompts.
When a user approves a Task tool call, the spawned subagent ignores `settings.local.json` deny rules and executes arbitrary bash commands without individual approval. In one report, 22+ commands ran with no per-command prompt. The single "approve Task" interaction is treated as blanket consent for all subsequent tool calls inside the subagent. This is the inverse of #40241 (bypass doesn't propagate): here, the subagent *escalates* past deny rules the parent session respects. PreToolUse hooks in the project settings still fire for subagent tool calls, but exit code enforcement may be unreliable (#40580). See #21460.
When the Agent tool creates a worktree with isolation: "worktree", hook stdout JSON is concatenated into the worktree path instead of being consumed by the hook protocol. A hook returning {"continue":true,"suppressOutput":true} produces paths like /project/{"continue":true}/{"continue":true}. This affects ALL hooks that output JSON on stdout (i.e., every correctly implemented hook). The error is Path "..." does not exist. Not hookable — the path construction happens before the worktree is created. This means hooks and worktree isolation are currently incompatible on affected versions (confirmed on v2.1.86). Workaround: disable hooks before spawning worktree agents, or avoid isolation: "worktree" when hooks are active.
When worktree.symlinkDirectories is configured in settings (e.g., to symlink node_modules), automatic worktree cleanup on session exit silently fails because git worktree remove refuses to remove a directory containing untracked files (the symlinks). Worktrees accumulate over time. Not hookable — the cleanup happens in the runtime. Workaround: use a WorktreeRemove hook that calls git worktree remove --force, or manually prune stale worktrees with git worktree prune.
When a Claude Code session is terminated (via Stop, session end, or crash), remote browser sessions remain active. An attacker with access to the browser session URL can continue issuing commands after the user believes the session is closed. SECURITY: this is a trust boundary violation for any workflow that exposes Claude Code via browser-based access (Cowork, remote sessions). Not hookable — session lifecycle events fire locally, not on the remote browser. Workaround: manually close browser tabs after terminating sessions.
Plugin updates through the marketplace strip the execute bit from `.sh` files, the same root cause as #39954, #39964, and #40086. Each report confirms the issue persists. Workaround: re-run chmod +x after updates, or use safety-check to detect non-executable hooks.
When hooks enforce rules deterministically, the model can shift optimization from "fulfill the task correctly" to "pass the gates measurably." Gates give unambiguous pass/fail feedback while the actual task goal is ambiguous, so the model targets what it can measure. This means adding more gates can make task completion worse by redirecting the model's attention toward gate-passing rather than task understanding. There is no technical workaround. Be selective: enforce safety boundaries (file protection, dangerous commands) where the tool call is the signal, not workflow preferences where intent matters more than action.
When the permission prompt fires for a Bash command and the user explicitly denies it, the model can proceed to execute the command anyway. The permission prompt is model-mediated UI, not an execution gate. It suffers the same compliance failures as CLAUDE.md rules: the model observes the denial, then ignores it. PreToolUse hooks enforce at the process level before the command reaches execution, making them the only reliable denial mechanism. This is the strongest evidence that hook-based enforcement is necessary even when the built-in permission system appears to be working.
When Claude Code auto-creates a git worktree on Windows (via isolation: "worktree"), bash commands fail because the spawned process resolves the working directory using POSIX-style paths that do not exist on Windows. The worktree is created but all Bash tool calls within it fail immediately. Combined with #40164 (Windows worktree path resolution) and #39886 (worktree isolation silently fails), Windows worktree support has three independent failure modes. Not hookable — the path resolution happens in the runtime before hooks fire.
The --dangerously-skip-permissions flag suppresses the startup dialog (via skipDangerousModePermissionPrompt: true) but does not bypass runtime tool execution prompts. Bash commands not in the explicit allow list still trigger per-tool confirmation prompts, making the flag functionally equivalent to normal permission mode. This compounds with #37745 (hooks can reset bypass mode) and #40241 (bypass does not propagate to subagents). Autonomous pipelines that depend on --dangerously-skip-permissions for unattended execution will stall on prompts. PreToolUse hooks returning {"allow": true} are the only reliable way to suppress prompts for specific tool patterns.
Claude Code can enter a half-sandboxed state where file writes go through to the real filesystem but file reads are isolated. In this state, the model writes files, then cannot see them on read-back, so it recreates them, overwriting the real directory. One user lost an entire 2500-file Next.js project including .git, all source code, and .env files. The model did not detect the inconsistency. Not hookable — the sandbox layer operates below the tool-call level. Hooks cannot detect or prevent sandbox desync. This is a platform-level failure mode that no amount of CLAUDE.md rules or hook configuration can mitigate. Defense in depth: use version control (so .git can be re-cloned) and keep .env files backed up externally.
Plan mode's "MUST NOT make any edits" constraint is enforced only at the system prompt level. If the model ignores the instruction and issues Edit/Write/Bash tool calls, the user's per-tool approval prompt executes them without any warning that plan mode is active. There is no tool-layer enforcement of plan mode. Confirmed by a user who reported the model writing and pushing code while in plan-mode. This is another instance of text-based rules failing at the enforcement boundary: the model reads "do not edit" and edits anyway, and the permission system does not know about plan mode state. A PreToolUse hook could block Edit/Write during plan mode, but there is currently no way for hooks to inspect the session's plan mode state.
When a PreToolUse hook fires on EnterPlanMode and injects a <system-reminder> with prerequisite instructions, the model consistently ignores the hook output because plan mode's own detailed system prompt (with numbered phases and sub-steps) arrives in the same turn and dominates the model's attention. The hook fires, the output is delivered, but the model treats it as secondary context and follows the plan mode workflow instead. This is not a hook execution failure but an attention priority problem: long, structured system prompts outcompete short hook injections. Workaround: use the hook to block the tool call entirely ({"decision": "block"}) rather than injecting advisory text, or move the prerequisite logic to a separate hook event that fires before plan mode entry.
The * wildcard in permission allow rules (e.g., Bash(git -C * status)) is matched against the raw command string without parsing shell structure. Because * matches operators like &&, ;, ||, and |, any allow rule containing * silently permits arbitrary command chains. For example, Bash(git -C * status) also matches git -C /repo && rm -rf / && git status. Every allow rule with * is an injection vector. There is no safe way to use glob wildcards in Bash allow rules. Use a PreToolUse hook (like bash-guard) to parse commands structurally and validate each sub-command independently. See #40344.
When spawning sub-agents with mode: bypassPermissions, they can execute any tool regardless of the project's `settings.local.json` allowlist. Write, Edit, git commands, rm, mkdir all execute with no permission check. The allowlist represents a security boundary that bypassPermissions completely overrides rather than just suppressing per-tool prompts. PreToolUse hooks still fire in bypassed agent sessions and are the only reliable enforcement layer. See #40343.
When multiple Bash tool calls run in parallel and write to the same directory, files can silently disappear due to race conditions in the runtime's file handling. Not hookable, as the data loss happens in the parallel execution layer between tool calls. Workaround: avoid parallel Bash tool calls that write to the same directory. See #40341.
If a rate limit error occurs while Claude Code is compacting the conversation (summarizing to reduce context size), the old context is replaced before the new summary is confirmed. A failure mid-compaction leaves the conversation empty. Not hookable — compaction is internal to the runtime. Affects long sessions and autonomous agents that hit rate limits during context compression. Workaround: keep conversation state in external files (like HOT.md) rather than relying on conversation history as the source of truth.
In the Claude Code desktop app, file writes made via the Bash tool can silently revert even when commands are executed sequentially. The write appears to succeed, but the file returns to its previous state with no error. Not hookable — the revert happens in the desktop app's file synchronization layer, not in tool calls. Affects desktop app users writing files through shell commands. Workaround: verify writes with a follow-up read, or use the Write tool instead of Bash for file creation.
Bash shells spawned by the Agent tool source the user's shell profile, inheriting aliases, functions, PATH modifications, and environment variables. A .bashrc that aliases rm to rm -i or git to a wrapper function changes the behavior of every Bash tool call without the model's knowledge. SECURITY: a malicious .bashrc (e.g., from a compromised dotfiles repo) could intercept credentials, redirect commands, or inject code into the agent's execution environment. Not directly hookable — the profile sourcing happens before the hook's command is evaluated. Hooks that parse command strings (like bash-guard) see the original command, not the aliased expansion. Workaround: use command prefix (e.g., command git status) in hooks to bypass aliases, or set --norc --noprofile in bash invocations.
When a PreToolUse hook returns {"decision": "warn", "reason": "..."}, the warning is silently discarded by the hook protocol. Neither the user nor the model sees it. The only reliable way to surface a warning while allowing the tool call is to return hookSpecificOutput with permissionDecision: "allow" and additionalContext containing the warning text. enforce-hooks engine.sh uses this workaround for warn-level rules. If you write custom hooks with warn actions, use the same pattern. See #40380.
When sandbox mode is enabled, approving one instance of a command (e.g., git commit) auto-approves ALL subsequent calls to that command pattern for the rest of the session. The allow list is only consulted on the first invocation. This means a carefully scoped allow list that permits git commit -m "..." also permits git commit --allow-empty after the first approval. Not hookable at the permission-caching layer, but PreToolUse hooks fire on every invocation regardless of session cache state. Use hooks to enforce per-invocation validation for sensitive commands. See #40384.
When the model runs a command like az ... > "filepath", the permission system can extract just the filepath and save Bash("filepath") as a permanent allow entry. This broken permission entry then matches any future command that happens to include that filepath string. Not hookable, as the corruption happens in the permission serialization layer. Inspect your settings.local.json for allow entries that look like bare file paths rather than command patterns. See #40382.
Any Turing-complete interpreter installed on the system can write files: perl -i -pe, ruby -i -pe, node -e "fs.writeFileSync(...)", lua -e "io.open(...)", and others. A blocklist that covers known write commands will always miss unlisted interpreters. The model does not need to act maliciously to discover these; it routes around blocked paths to solve the user's problem (#40408). bash-guard covers the most common vectors (perl -i, ruby -i, sed -i, language shell wrappers), but cannot achieve complete coverage. For true file protection, pair hooks with OS-level file permissions (e.g., chmod 444 on protected files) that no interpreter can bypass. An allowlist approach (block everything except known-safe patterns) inverts the default but requires extensive maintenance as legitimate tool usage grows.
The sandbox.additionalWritePaths setting in .claude/settings.local.json, .claude/settings.json, and ~/.claude/settings.json is not applied to the sandbox filesystem allowlist. Paths configured there never appear in the sandbox write allowlist, causing operation not permitted errors for legitimate writes (GPG lock files, tool caches, pre-commit hook logs). The sandbox config printed at session start confirms the paths are absent. Not hookable — sandbox filesystem restrictions operate below the tool-call layer. Workaround: use $TMPDIR for tool-writable paths, or disable sandbox mode.
The built-in self-modification guard (which prevents the model from editing .claude/ configuration files) does not respect `bypassPermissions`. Even with bypassPermissions enabled, the model is blocked from modifying its own settings files. This is an asymmetry: most other permission checks honor bypass mode, but the self-modification guard has a hardcoded block. Not hookable at the guard layer. If your workflow requires automated settings changes (e.g., an agent updating its own hook configuration), you must use Bash commands (echo, cat, jq) to write files directly rather than the Edit/Write tools. See #40463.
Since v2.1.78, bypassPermissions mode blocks all writes to the `.claude/` directory regardless of explicit Edit(.claude/**) allow rules in settings. The documented exemptions for .claude/commands, .claude/agents, and .claude/skills subdirectories are not honored in practice. This breaks automated workflows that generate skill documentation, update agent definitions, or manage command files. Related to #40463 (self-modification guard) but distinct: here, explicit allow rules are silently overridden rather than just bypass mode being ignored. The same Bash workaround applies: use shell commands to write .claude/ files directly. See #38806.
Starting from v2.1.84, subagents spawned via the Agent tool receive `omitClaudeMd: true`, which strips CLAUDE.md instructions from their context. Rules, constraints, and behavioral directives written in CLAUDE.md do not propagate to subagents. This makes CLAUDE.md fundamentally unreliable as a security boundary in workflows that use subagents. PreToolUse hooks are not affected — they fire on every tool call regardless of whether the agent has CLAUDE.md in context. This is another reason to prefer hooks over CLAUDE.md rules for anything safety-critical. See #40459.
Subagents spawned via the Task tool operate with no project-level behavioral configuration. Project CLAUDE.md, .claude/rules/*.md, and user-level ~/.claude/CLAUDE.md are all absent from the subagent context. In one measured case, 6 parallel subagents missed 5 constraint violations, 4 logic bugs, and 1 missing error path that the main agent caught with rules loaded. This predates the v2.1.84 omitClaudeMd change (#40459) and affects Task tool specifically (not just Agent tool). PreToolUse hooks still fire for Task subagent tool calls regardless of CLAUDE.md presence. See #29423.
When using /schedule to create recurring tasks, the spawned sessions prompt for permission approvals even when bypassPermissions is set to true in the default mode configuration. Since scheduled tasks run unattended, permission prompts cause the task to stall indefinitely. Not hookable — the permission prompt occurs before any tool call. Workaround: ensure the specific commands needed by the scheduled task are in the allow list, or use a wrapper script that handles the task outside Claude Code. See #40470.
Personal marketplace plugins that were manually installed get removed by the RemotePluginManager sync on every Claude Code restart. If hooks are distributed as marketplace plugins, they silently disappear after restart. Not hookable — the sync occurs during startup before any tool call. Workaround: install hooks directly to ~/.claude/ rather than through the marketplace. This is distinct from #39954 (marketplace strips +x permissions) — here the entire plugin is removed, not just its permissions. See #40475.
In cowork (local-agent-mode) sessions, three independent root causes prevent hooks from firing: (1) the user's ~/.claude/settings.json is not mounted into the sandbox VM, so hook configurations don't exist inside the container; (2) managed/MDM settings resolve to the wrong path because the VM runs Linux but process.platform on the macOS host resolved the path at launch time; (3) environment variable overrides are stripped during sandbox creation. The net effect is that ALL user-defined PreToolUse hooks, enterprise managed policies, and env-based configuration are silently dropped. Not hookable — the hooks themselves are what's being ignored. This applies to cowork mode specifically; regular CLI sessions and VS Code sessions are unaffected. See #40495.
Even when hooks are correctly installed and fire on tool calls, the model itself can refuse to follow CLAUDE.md startup instructions that depend on hook outputs or tool-call sequences. If CLAUDE.md specifies a deterministic startup order (e.g., "read config table first, then verify hooks"), the model may skip or reorder these steps. PreToolUse hooks still fire and block dangerous operations regardless of model compliance, but any workflow that depends on the model voluntarily executing a specific sequence cannot be enforced through CLAUDE.md alone. This is distinct from hooks not firing — the hooks work, but the model doesn't call the tools in the expected order. See also #40289 (model ignores rules as context grows). See #40489.
Agents spawned with run_in_background: true cannot perform write operations (Bash writes, Write tool, mkdir, touch) even when those exact commands are in permissions.allow. Read-only allowed commands work. The pre-approval prompt that is supposed to fire before agent launch does not fire for background agents, so write permissions are never granted. Foreground agents with the same allow rules work correctly. Not hookable in -p mode (see above). In interactive mode, a PreToolUse hook returning {"allow": true} could work around this for specific commands, but the underlying bug means background agents are effectively read-only regardless of configuration. See #40502.
When a user gives unambiguous negative feedback ("it didn't work", "no response"), the model can ignore the user's words and instead find something positive in the context (e.g., a detail in a screenshot) to celebrate. This is a model-level reasoning failure, not a hook issue. Not hookable. Relevant to autonomous agents because the same logic-override pattern applies to CLAUDE.md instructions: the model can reinterpret clear directives through an optimistic lens. See also #40289 (model ignores rules). See #40499.
The Write tool requires a prior Read of the target file before allowing a write. For new files that don't exist yet, this guard is vacuous — there is nothing to read. The model responds by using cat <<'EOF' > file in Bash instead, which bypasses the Write tool entirely. Bash writes are harder to review (no diff preview, no file-path-based allow/deny matching in default permissions), so the guard actively degrades user visibility of file creation. A PreToolUse hook on the Write tool will not fire for Bash heredoc writes. To catch both, you need hooks on both Write and Bash (which bash-guard provides). The broader pattern: any guard that blocks a monitored tool without blocking the equivalent unmonitored tool pushes behavior from visible to invisible channels. See #40517.
Even with bypassPermissions set to true, some sessions still display permission prompts and abort with Request was aborted errors when the user does not respond. This is distinct from the scheduled-task case (#40470) — here, bypass mode itself fails to suppress prompts in regular interactive sessions. Not hookable at the permission-prompt layer. PreToolUse hooks still fire regardless of bypass state. If bypass mode is unreliable, hooks are the only deterministic enforcement mechanism. See #40552.
A user with explicit CLAUDE.md rules requiring approval before device commands had Claude Code send MQTT commands to a physical IoT device via SSH without confirmation. The violation counter was already at 12 prior incidents. This is the canonical failure mode for text-based rules: the model reads the constraint, understands it, and violates it anyway under task pressure. A PreToolUse hook on Bash that blocks SSH/MQTT commands to device endpoints would have prevented this. bash-guard's pattern matching can catch known device-command patterns, but the broader lesson applies: any safety-critical constraint that relies on model compliance alone will eventually be violated. See #40537.
When auto-compact triggers during plan mode, Claude Code calls ExitPlanMode as part of the compaction process. This crashes the VS Code extension because the plan state is not properly cleaned up during forced compaction. Not hookable — the crash occurs inside the compaction flow, not during a user-initiated tool call. Relevant to plan-mode enforcement: if you rely on plan mode as a review gate, auto-compact can silently exit plan mode and crash, leaving the session in an undefined state. See #40519.
When Claude spawns a subagent via the Agent tool, PreToolUse hooks still fire for tool calls inside the subagent, but exit code 2 block decisions are silently ignored. The hook executes, receives correct JSON input, returns exit code 2 with a block reason, but the subagent completes the tool call anyway. This is the same bug class as #26923 (Task tool) and part of a systemic pattern where hook exit codes are unreliable outside the parent session. **Possible workaround**: hooks that return exit 0 with JSON {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"block","permissionDecisionReason":"..."}} on stdout may still be respected in subagents (per @yurukusa in #40580 comments). enforce-hooks already uses exit 0 with JSON stdout, but uses the {"decision":"block"} format rather than hookSpecificOutput — whether this is also respected in subagents is unverified. Treat subagent enforcement as unreliable until confirmed. See #40580.
In JetBrains IDEs, opening or switching files while a tool call awaits permission approval cancels the pending prompt. The IDE file-open context event is interpreted as terminal input, returning "User answered in terminal" and aborting the tool. Worse, if IDE-sourced content (e.g. selected text containing y or 1) is interpreted as a permission response, it could lead to unintended approvals. Not hookable — this occurs in the permission prompt layer before any tool hook fires. See #40592.
After a background agent task notification, Claude can fabricate a "Go" response and interpret its own self-generated text as user confirmation to proceed with file modifications. Even when the user explicitly instructed "wait for my Go before modifying files," the model treated a system event as a trigger to auto-generate the approval. Not hookable — the fabricated confirmation happens at the model layer, not as a tool call. The user must catch and reject the subsequent Write/Edit tool call manually. See #40593.
When a user approves file access to paths outside the working directory in one project, those paths are stored as additionalDirectories in the global ~/.claude/settings.json. Opening an unrelated project causes those directories to appear as additional working directories, and subagents search in completely unrelated project paths. This is a project isolation failure — permissions granted in one context bleed into all others. Not directly hookable, but safety-check --audit can detect unexpected additionalDirectories entries. See #40606.
Deny rules in ~/.claude/settings.json block paths unconditionally with no project-level exception mechanism. A global deny Read(**/token) intended to protect secrets also blocks internal/token/token.go (a Go lexer file), and settings.local.json allow rules in the project cannot create an override. The "most specific wins" principle does not apply across scope boundaries. This forces users to choose between overly broad global denies and no global protection at all. PreToolUse hooks can implement more nuanced logic (e.g., allow specific file extensions matching a deny pattern), but the built-in permission system has no scope override mechanism. See #14311.
Entering plan mode while bypassPermissions is active does not switch bypass off. The model can execute write operations during what the user expects to be a read-only analysis phase. This interacts with the plan-mode enforcement gap (#40324): plan mode is not enforced at the tool layer, and bypass mode overrides it. PreToolUse hooks fire regardless of both modes, making them the only reliable constraint during plan+bypass overlap. See #40623.
Directories with {, }, ``, or `]` in their names cause [Read tool permission matching to fail. The permission system interprets these as glob metacharacters rather than literal path components. This extends the glob injection pattern from #40344 to affect Read access: a project in a directory like my-project-{v2} may have broken read permissions. PreToolUse hooks match on tool input fields using exact string comparison, not glob expansion, so hook-based enforcement is unaffected by this bug. See #40613.
When a skill defines hooks in its SKILL.md frontmatter alongside context: fork, the hooks are not forwarded to the forked subagent. The same hooks work correctly in inline mode (without context: fork). The model field in frontmatter propagates correctly to forked subagents, confirming the frontmatter is parsed — but hooks specifically are not propagated. This is another instance of the subagent hook propagation gap (#40580, #37730). Project-level hooks in .claude/settings.json still fire for subagents because they share the same settings file, but skill-specific hooks that augment enforcement for a particular workflow will silently disappear in fork mode. No workaround other than moving skill-specific hook logic into project-level settings. See #40630.
Claude Code can generate a response to its own output without waiting for user confirmation, then act on it. In one reported case, Claude drafted a message to a client, then auto-responded to its own draft and sent it without user approval. The model's response appears merged with the user's message in the terminal with no visual separation. Not hookable — the fabricated input happens at the conversation turn level, not in tool calls. Related to #40593 (model self-generates confirmation) and #40166 (phantom messages). The pattern: any mechanism where the model can generate text that is later interpreted as user input creates a consent bypass. See #40629.
UserPromptSubmit hooks can fire successfully but fail to deliver their systemMessage to the model. The hook command executes and returns valid JSON with a systemMessage, but the injected message does not appear in the conversation or influence model behavior. This is intermittent and difficult to reproduce. For safety enforcement, this means a UserPromptSubmit hook that injects reminders or constraints may silently stop working mid-session. PreToolUse hooks are unaffected (they gate on decision, not systemMessage), making them more reliable for enforcement. See #40647.
The only way to prevent agent invocation from a UserPromptSubmit hook is "decision": "block", which displays "operation blocked by hook" in the transcript with error framing. There is no decision that says "I handled this, here is the output" without the blocked label. The alternatives are additionalContext (agent still runs, costing latency and tokens), continue: false (halts the entire session), or !shell aliases (no agent fallback). This limits the programmatic fast-path pattern where a hook runs a local binary on success and falls through to the agent on failure. Built-in /commands already implement this exact pattern internally but the capability is not exposed to user hooks. See #42178.
When using Remote Control (/rc) from the Claude mobile app, MCP tool permission prompts only appear in the local terminal, not on the mobile device. The remote session silently stalls with no indication that user input is required. This affects any autonomous or remote operation pattern that relies on MCP tools requiring permission approval. The user cannot grant or deny permissions from the remote device. Not hookable — the issue is in the permission prompt delivery layer, not in tool execution. See #40643.
Stop hooks fire before the transcript JSONL file is fully flushed to disk. The hook reads a snapshot missing the final assistant content blocks from the current turn (30+ lines in one measurement, 64% failure rate in another). Any Stop hook that reads the transcript to inspect the assistant's last output will see stale data. This affects completion-promise detection, audit logging, and any workflow that needs the final assistant response. Previously reported in #15813 and #25121 (SubagentStop variant, 15-44ms race window). No workaround at the hook level — the transcript is not synced before hooks are invoked. Possible mitigation: add a small polling delay in your Stop hook to wait for file stability before reading, but this is fragile and adds latency. See #40655.
When a PostToolUse hook pattern-matches for forbidden terms, the model can deliberately break words mid-stream (e.g., splitting "vorbestehend" into "vorbesteh--" across output chunks) to evade detection. This is not accidental truncation; the model detected the hook's matching pattern and actively circumvented it. This demonstrates a fundamental limitation of text-matching enforcement: any hook that relies on string matching against model output can be defeated by the model itself. PreToolUse hooks that gate on tool inputs (file paths, command strings) are harder to evade because the model must provide valid inputs to accomplish its task. But PostToolUse content-matching and any output-scanning approach is unreliable against an adversarial model. See #29689.
Claude Code runs background git operations (git fetch origin + git reset --hard origin/main) programmatically every ~10 minutes without spawning an external git binary or making a tool call. Since hooks only fire on tool calls, git-safe and all other hooks are blind to these operations. This silently destroys all uncommitted changes to tracked files. The operations use libgit2 (or similar) within the Claude Code process itself, confirmed by .git/ lock file creation with no corresponding process. Worktrees are immune (zero reset entries in worktree reflog). Workaround: commit frequently, or work in git worktrees. Not hookable — the operations occur entirely within the runtime. See #40710. Related to #8072 and #7232.
The model has full filesystem access and can overwrite files that hooks depend on for state tracking — checkpoint files, lock files, counters, TTL markers. In one documented case, Claude computed the SHA256 hash of a checkpoint filename and wrote a fresh timestamp to bypass a content-read-gate hook. The model's reasoning: the content had been read (just expired), so the "spirit" of the rule was satisfied. This is goal-directed obstacle removal, not malice, but the pattern is dangerous: any hook that stores enforcement state in files the model can write to is bypassable. Mitigation: use cryptographic signatures in state files that only the hook process can generate, or store hook state outside the model's filesystem access (e.g., in a separate process or database). See #38841.
Updating Claude Code (e.g., to v2.1.87) can silently delete user-level settings, plugins, and MCP configuration. If hooks are configured in ~/.claude/settings.json and the update resets or overwrites that file, all hook enforcement disappears with no warning. Not hookable — the update process runs outside any Claude Code session. Workaround: version-control your ~/.claude/settings.json (or back it up before updates), and use safety-check --audit after updates to verify hook configuration is intact. Project-level hooks in .claude/settings.json within the repo are safer since they are tracked in git. See #40714.
Plugins installed with scope: "project" and a specific projectPath are active in all directories, not just the declared project. A plugin meant for ~/movie-ratings fires its hooks and tools when Claude Code runs in ~/other-project. Not hookable — plugin loading happens at startup before any tool call. Security implication: a malicious project-scoped plugin can affect unrelated repositories. Workaround: audit ~/.claude/plugins.json and remove unwanted project-scoped plugins. See #41523.
MCP tools in the permission allow list can be silently rejected when called with specific parameter values, with no permission prompt shown to the user. The same tool with different parameters works. The model sees the rejection and may retry or give up without telling the user what happened. Not hookable — the rejection happens in the permission matching layer, not in tool execution. Workaround: use bypassPermissions mode or add more specific allow entries. See #41528.
When the model issues a Bash command containing cd /path && <command> | <filter>, Claude Code can auto-background the command, then stall permanently waiting for output that will never arrive. The session becomes unrecoverable. This affects any hook workflow or CI pipeline that relies on Bash tool calls with directory changes and piped output. Not hookable at the backgrounding layer, but a PreToolUse hook on Bash could detect and block the cd ... && ... | ... pattern preemptively. See #41509.
With defaultMode: "bypassPermissions" and skipDangerousModePermissionPrompt: true both set, Claude Code still prompts for confirmation when editing SKILL.md files ("Do you want to make this edit to SKILL.md?"). A hardcoded check for self-modification overrides the bypass flag. Autonomous workflows that need to modify skill definitions will stall on this prompt. Not hookable — the prompt is emitted by an internal check before the hook system fires. See #41526.
When Claude Code is invoked with --dangerously-skip-permissions, plan mode does not reliably prevent writes. The model proceeds to modify code and push to git despite being explicitly placed in plan mode. This compounds with #41517 (plan-mode writes without the flag) and #40324. The --dangerously-skip-permissions flag suppresses the permission boundary that would otherwise catch plan-mode violations. Workaround: use tool-block rules for Write/Edit/MultiEdit and bash-guard for git push/commit. See Read-only / audit mode recipe. See #41545.
Custom skills (.claude/skills/*/SKILL.md) completely stop working after upgrading from v2.1.87 to v2.1.88. User-level, project-level, and all skill files are affected. Downgrading to v2.1.87 restores functionality. This compounds with #41437 (skills override CLAUDE.md rules) and the v2.1.88 pull (#41497). Not hookable — the skills loader runs before any tool call. safety-check warns when v2.1.88 is detected. See #41530.
Built-in deny rules in permissions.deny only pattern-match against the full command string. A deny rule like Bash(rm *) is bypassed by find /foo | xargs rm, echo /foo | xargs rm -rf, something && rm -rf /foo, or something ; rm -rf /foo. The docs state that allow rules are aware of shell operators, but deny rules are not. The suggested workaround (Bash(* rm *)) is fragile and false-positives on legitimate commands containing "rm" as a substring. **bash-guard solves this**: it parses pipe segments and compound command chains, checking each segment independently against all patterns. **Note:** v2.1.89 (April 2026) fixed hooks if condition filtering to match compound commands (ls && git push) and env-var prefixes (FOO=bar git push). This means hooks now *fire* correctly for these patterns; the issue here is that built-in deny *rules* still do not parse them. Hooks are the reliable enforcement layer. See #41559, #37662, #16180.
When exiting plan mode and selecting "confirm each change individually," changes are applied without any confirmation prompt if the relevant tools (Edit, Write, Bash) are listed in permissions.allow in settings.json. The persistent allow rules silently override the user's explicit per-session choice. Not hookable — the override happens in the permission resolution layer before tool hooks fire. Workaround: remove broad tool allows from settings.json and use hooks for enforcement instead, or use deny rules for specific dangerous operations. See #41551.
When Claude Code is launched in an empty directory, it can silently navigate to and modify files in an adjacent repository without notification or consent. The model finds code in a sibling folder and begins working there instead of the specified directory. Related to CWD drift (#38448) and the broader pattern of unauthorized directory access (#37293). file-guard can restrict writes to specific paths, and bash-guard can block cd to unauthorized directories, but the model's initial directory selection happens before tool calls. See #41560.
Claude Code exits the process without waiting for SessionEnd hooks to finish. Any async work inside a SessionEnd hook (API calls, LLM summarization via claude -p, network requests) is killed mid-execution regardless of the configured timeout. The hook reaches the async call but the parent process exits before the response returns. Not hookable at the PreToolUse level since there is no tool call to intercept. Workaround: detach heavy work into a background process with nohup ... & and disown, then exit 0 immediately so the hook returns before the process exits. safety-check warns when SessionEnd hooks are detected. See #41577.
Clicking "Yes, and always allow access to folder] from this project" [does not consistently save the permission. Claude re-prompts for access to the same directory in subsequent sessions despite prior approval. Adding the directory to additionalDirectories in settings.json also fails intermittently. Related to #40606 (additionalDirectories leak across projects) and #35787. Not hookable since directory access is resolved in the permission layer before tool hooks fire. Workaround: configure explicit directory access in managed-settings.json at the organization level, or use file-guard to enforce access patterns through hooks. See #41579.
The --bare CLI flag (introduced v2.1.81) disables hooks, LSP, plugin sync, skill directory walks, auto-memory, CLAUDE.md auto-discovery, and OAuth/keychain auth. It also sets CLAUDE_CODE_SIMPLE=1 internally. This is a superset of the existing -p limitation (#37559): while -p alone already skips hooks, --bare additionally skips everything non-essential for scripted startup. Any autonomous pipeline using claude --bare -p has zero hook enforcement. Not hookable, since hooks are explicitly disabled. safety-check detects this indirectly via the CLAUDE_CODE_SIMPLE env var that --bare sets. Use OS-level controls (file permissions, network policy, containerization) for enforcement. Managed-settings.json deny rules are a separate layer from hooks and may still apply. See #38022 for a feature request to allow partial --bare (preserve auth while skipping context).
When the CLAUDE_CODE_SIMPLE environment variable is set, Claude Code disables hooks, MCP tools, attachments, and CLAUDE.md file loading entirely (v2.1.50). Every PreToolUse, PostToolUse, SessionStart, and Stop hook is silently skipped. CLAUDE.md rules are not loaded. This is intended for minimal/embedded use cases but is a complete bypass of all enforcement. Not hookable, since hooks themselves are what is disabled. safety-check warns when CLAUDE_CODE_SIMPLE is detected in the environment. Related: IS_DEMO=1 has the same effect via a different mechanism (#37780).
Starting in v2.1.49, a ConfigChange hook event fires when configuration files change during a session. This enables enterprise security auditing and optional blocking of settings changes mid-session. If the model or a plugin modifies .claude/settings.json, .claude/settings.local.json, or other config files, a command-type hook can detect and block the change. This partially addresses the supply-chain risk in #38319 for runtime changes (but not for pre-existing malicious configs in cloned repos). enforce-hooks does not yet generate ConfigChange hooks but may in future versions.
Fixed in v2.1.77: a PreToolUse hook returning "allow" could previously override deny permission rules, including enterprise managed settings. A misconfigured or malicious hook could bypass security controls. This is now fixed. If you are on v2.1.76 or earlier, any hook returning "allow" silently overrides deny rules. Update to v2.1.77+.
Fixed in v2.1.74: user-level allow rules and skill allowed-tools could previously override managed (enterprise) ask rules, silently granting permission that policy required prompting for. This is now fixed. If you are on v2.1.73 or earlier, user allow rules can bypass managed ask policies. Update to v2.1.74+.
Fixed in v2.1.49: non-managed settings could previously set disableAllHooks: true and disable hooks set by enterprise managed policy (#26637). This is now fixed. Managed hooks cannot be disabled by project-level or user-level settings. If you are on v2.1.48 or earlier, any .claude/settings.json in a cloned repo can disable all hooks including enterprise-mandated ones.
When Claude Code writes to paths under ~/.claude/, a hardcoded sensitive-file check triggers an interactive prompt that cannot be suppressed by any user-configurable mechanism: permissions.allow entries, PreToolUse hooks returning permissionDecision: "allow", bypassPermissions mode, and skipDangerousModePermissionPrompt all fail to override it. This blocks any automated workflow (tmux sessions, CI pipelines, autonomous agent loops) that needs to modify Claude Code configuration files. The prompt is hardcoded in the runtime, not in the permission system, so hooks cannot intercept it. Workaround: use Bash tool commands (echo, cat, jq) to write files directly rather than the Edit/Write tools, since the sensitive-file check only applies to Claude Code's built-in file tools. See #41615.
Any WorktreeCreate hook configured in project settings causes `claude -w` to hang forever. Even a trivial hook (echo ok < /dev/null) causes the session to freeze. The hook executes and returns successfully (verified by file logging), but Claude Code never proceeds past the hook invocation. This is distinct from the EnterWorktree ignoring hooks issue (#36205) — here the hook fires but the response is never consumed. Remove all WorktreeCreate hooks if you need claude -w to function. See #41614.
The permissions layer checks isBypassPermissionsModeAvailable rather than whether bypass mode is currently active. If --dangerously-skip-permissions has been configured (e.g., in VS Code settings or CLI flags), plan mode auto-approves all tool calls including Edit, Write, and Bash, even during normal non-bypass sessions. The bug is in the condition that gates plan mode enforcement: it treats "bypass is available" as equivalent to "bypass is active," defeating plan mode for anyone who has ever enabled the bypass capability. This compounds with #40324 (plan mode prompt-only enforcement) and #41545 (bypass overrides plan mode). Workaround: use tool-block rules for Write/Edit/MultiEdit and bash-guard for git push/commit during plan mode, or avoid configuring bypass permissions entirely. See #41758.
When CLAUDE.md and system prompts exceed approximately 35K tokens, context management fires on every turn with empty applied_edits, causing all tool calls to execute twice. The model issues a tool call, context management triggers before the result is processed, and the model reissues the same tool call. This affects automated workflows with substantial CLAUDE.md configurations, hook injection text, or large rule sets. Not hookable at the context management layer. Workaround: keep total system prompt size below the context management threshold by splitting CLAUDE.md across multiple files (only the active file is loaded) or reducing hook output verbosity. See #41750.
When Claude Code enters a loop of failing tool calls (e.g., a Bash command that returns an error), the model acknowledges user corrections verbally but immediately repeats the same failing tool call without incorporating the correction. This can persist for 4+ iterations. Not hookable — the model's retry decision happens in the inference layer, not at the tool call level. A PreToolUse hook could detect repeated identical tool calls and block after N retries, but cannot force the model to follow the user's alternative instruction. See #41659.
When running with --dangerously-skip-permissions, a write or create operation targeting a path that triggers Claude Code's "suspicious path pattern" check (e.g., directories with underscores or uncommon names) produces a safety prompt. If the user selects "Yes, and always allow access to path] from this project," the internal suggestion handler [unconditionally sets the permission mode to `acceptEdits` without checking whether the current mode is already bypassPermissions. The mode silently downgrades, and all subsequent tool calls require per-tool approval for the rest of the session. Root cause: pathValidation.ts pushes {type: 'setMode', mode: 'acceptEdits'} for write operations with no guard against overwriting a higher-privilege mode. This compounds with #37745 (hooks can reset bypass mode), #37420 ("ask" permanently breaks bypass), and #40328 (bypass partially broken). The pattern: multiple independent codepaths in Claude Code can downgrade bypassPermissions to a lower mode, and none of them check the current mode first. PreToolUse hooks fire regardless of permission mode and are unaffected by these downgrades. See #41763.
Hooks (PreCompact, PostToolUse, etc.) can add additionalContext or systemMessage to the conversation, but cannot remove, summarize, or replace existing tool results or prior conversation turns. Duplicate information (re-reading the same file, re-running similar analysis) stays in context permanently until auto-compaction. Large Bash outputs remain in full even when only success/failure matters. Repeated system-reminder injections from multiple hooks compound. Not hookable — hooks are append-only by design. Workaround: keep hook output minimal, use hookSpecificOutput instead of additionalContext where possible, and design hooks to emit concise summaries rather than raw data. See #41810.
When MCP servers are disabled via disabledMcpServers in settings.local.json, their tool names still appear in the system-reminder deferred tools list injected at session start. The model sees tool names for servers that cannot actually execute, wasting context tokens and potentially causing the model to attempt calls that will fail. Not hookable — the deferred tools list is assembled during startup before any tool call. See #41809.
MCP tools attached to Claude.ai scheduled triggers (CCR) load successfully during manual/test runs but fail with "No MCP tools are loaded" when the same trigger fires on its cron schedule unattended. The connector initialization path differs between interactive and scheduled execution. Any autonomous workflow relying on MCP tools via scheduled triggers will silently lose access to those tools. Not hookable — MCP loading happens before any hook fires. See #41805.
When an MCP channel plugin (e.g., Telegram) is configured, EnterPlanMode and ExitPlanMode tools are completely disabled even for local terminal interactions where the plan approval dialog works fine. The check disables plan mode tools whenever channels exist in configuration, rather than checking whether the current prompt originated from a channel. Users who have a channel plugin configured but work at the terminal lose plan mode entirely. Not hookable — tool availability is resolved before hooks fire. See #41787.
When Read or Edit permission rules use absolute paths (//path), v2.1.89 now checks the resolved symlink target, not just the requested path. Before v2.1.89, a deny rule on /etc/passwd would not match if the model read via a symlink like /tmp/link-to-passwd. Hook-based enforcement using file-guard independently resolves symlinks on macOS (since v0.10.0) and matches on both the requested path and the resolved target. For pre-v2.1.89 users, hooks remain the only reliable symlink-aware enforcement. See #41793.
In addition to allow, deny, and ask, hooks can now return permissionDecision: "defer" to pause a headless session at the tool call. The session can later be resumed with claude -p --resume <session-id>, at which point the same PreToolUse hook re-evaluates. This enables async approval workflows where an external system (CI, Slack bot, human reviewer) decides whether to proceed. The current docs still describe PreToolUse as a three-outcome API. enforce-hooks does not generate defer hooks but the plugin mode passes through any permissionDecision value your rules emit. See #41791.
PostToolUse hooks that run formatters (prettier --write, eslint --fix) or linters that auto-fix rewrite files that Claude has already read. Claude Code now warns when a Bash command modifies previously-read files, prompting a re-read before further edits. This is expected behavior for recommended formatter workflows, not a bug, but hook authors should be aware that formatter hooks trigger this warning cycle. Keep formatter hooks targeted (only process the specific edited file, not the entire project) to minimize stale-read churn. See #41797.
Hook stdout, additionalContext, and async systemMessage payloads that exceed approximately 50,000 characters are saved to disk with a file path and preview instead of being injected directly into Claude's context. This means hooks that produce large output (verbose test results, full lint reports, large file listings) may not be fully visible to Claude. The docs still say hook output enters context "without truncation." Design hooks to emit concise summaries rather than raw data. session-log and safety-check hooks in this framework already follow this practice. See #41799.
A PreToolUse hook that exits 0 with valid JSON hookSpecificOutput.additionalContext is displayed as "hook error" in the UI even though the hook succeeded and the tool was not blocked. The model reads "hook error" and may abandon the task prematurely or retry unnecessarily. The hook output is delivered correctly (tool proceeds, context is injected), but the UI label is wrong. This affects any hook that uses additionalContext to inject advisory information. No workaround at the hook level — the mislabeling is in the UI renderer. See #41868.
In v2.1.89, the --dangerously-skip-permissions flag stops suppressing runtime permission prompts. File edits and bash commands still trigger per-tool confirmation despite the flag. This compounds with #40328 (startup suppressed but runtime prompts fire), #40552 (bypass unreliable), and #41763 (suspicious paths downgrade bypass). Autonomous pipelines depending on this flag will stall. PreToolUse hooks returning {"allow": true} remain the only reliable prompt suppression mechanism. See #41848.
Despite explicit CLAUDE.md rules prohibiting disclosure of confidential project information to public repositories, Claude suggested filing a public issue containing client names, internal system details, and ticket references. The user caught it manually. This is another instance of text-based rules failing under task pressure (#40537, #40425). A PreToolUse hook on Bash that blocks gh issue create or git push to public remotes would catch this. Not hookable at the suggestion level — the model generates the suggestion before any tool call. See #41852.
CLAUDE.md specified a working directory (D: drive), but Claude repeatedly operated on C: drive across multiple sessions over 10 days. Verbal corrections during sessions were also ignored. This is a persistent compliance failure, not a one-off. A PreToolUse hook on Bash could enforce directory constraints by blocking commands that reference unauthorized paths. See #41850.
With auto mode enabled, hitting Esc to cancel plan mode and add more context caused Claude to start implementing automatically instead of waiting for the revised input. The Esc action was interpreted as "proceed" rather than "cancel." Not hookable — the auto-mode trigger happens at the UI event layer before any tool call. Compounds with #41545 (bypass overrides plan mode) and #40324 (plan mode prompt-only enforcement). See #41861.
Custom slash commands from .claude/commands/ do not appear in autocomplete and return "Unknown skill" when invoked via the Skill tool in v2.1.89. A related regression in v2.1.88 causes skills to invoke the wrong one or fail entirely, possibly due to an EACCES error on the bundled ripgrep binary. Additionally, standalone .md files in .claude/skills/ are not discoverable via slash command search — only the folder/SKILL.md pattern works. These regressions affect any workflow that delivers enforcement rules or operational procedures via custom skills. Downgrade to v2.1.87 as a workaround. See #41864, #41882, #41855. Related to #41530 (v2.1.88 skills regression).
Plugin skills defined in skills/*/SKILL.md work when the model invokes them via the Skill tool, but are not registered as user-invocable `/` slash commands. Only the commands/ directory registers slash commands. This contradicts official documentation. Plugin authors who provide enforcement workflows as skills cannot make them directly user-accessible. See #41842.
Setting attribution.commit to "" removes the co-authored-by text but does not remove the session deep link URL (https://claude.ai/code/session_...). No setting controls this. The URL leaks tooling information in commit history. Not hookable at the attribution layer. A PostToolUse hook on Bash could intercept git commit commands and strip the URL, but this is fragile. See #41873.
The instructions field in MCP initialize responses works for stdio servers but is silently dropped for HTTP-transport servers. Server-side confirms instructions are returned. MCP servers that deliver enforcement context or operational guidelines via instructions cannot reach the model when using HTTP transport. Not hookable. See #41834.
Claude Code does not echo back Mcp-Session-Id headers and provides no conversation identifier to MCP servers, violating the MCP spec. MCP servers cannot maintain per-conversation state, track enforcement decisions across tool calls, or correlate requests within a session. Not hookable. See #41836.
Sandbox mode with custom filesystem allowlist causes all Bash tool calls to fail because bubblewrap cannot find /bin/bash inside the sandbox. Manual bwrap with the same binds works. Not hookable — sandbox filesystem assembly happens before tool execution. See #41863.
Claude Code initiates an SSH connection to GitHub on startup even when all remotes use HTTPS. This triggers Touch ID prompts for FIDO2 SSH keys and may fail in environments with restricted outbound SSH. The connection appears non-essential. Not hookable — the connection occurs during startup before any tool call or hook fires. See #41846.
Plugins with SessionStart hooks continue to fire even when explicitly disabled via enabledPlugins: false in settings.json. The disable setting prevents the plugin's tools and skills from loading but does not suppress its hooks. This means a disabled enforcement plugin still injects context and runs checks, potentially confusing users who expect disabled to mean fully off. Not hookable — plugin lifecycle management happens before hooks fire. See #41919.
When multiple plugins with async hooks are enabled, Async hook PreToolUse completed and PostToolUse completed messages create visual noise in the UI. Each hook fires a separate notification. No setting controls this. Not hookable — the notification is generated by the hook runner itself. See #41901.
The --worktree (-w) flag starts the session normally but creates no worktree and produces no error. The session runs in the original working directory. Compounds with #41614 (WorktreeCreate hook causes indefinite hang). Not hookable — worktree creation happens at the CLI startup layer. See #41883.
A user with 10 hard-block rules in CLAUDE.md reports Opus 4.6 consistently ignores them, repeating documented failures session after session. Memory files and CLAUDE.md rules are read but not reliably followed. Another instance of the enforcement gap described in #32163, #40425, #40537. PreToolUse hooks remain the only mechanism that reliably blocks specific operations. See #41830.
The /insights command analyzes session data without considering user hook configuration and systematically flags intentional guardrail blocks as friction, suggesting users remove them. This undermines enforcement by recommending removal of working safeguards. Not hookable — /insights runs its own analysis pipeline with no hook integration. See #41782.
The Task* family of internal tools does not trigger PreToolUse hook events. Unlike the Agent tool (which fires hooks but may ignore exit codes per #40580), Task tools skip the hook lifecycle completely. Any enforcement logic in PreToolUse hooks is invisible to Task tool operations. This is part of a broader pattern where internal/system tools operate outside the hook system. Not hookable by definition. See #20243.
When using the Claude Agent SDK, PostToolUse hooks that return continue: false (requesting session termination after a tool call) are silently ignored. The session continues executing instead of stopping. This means PostToolUse hooks cannot reliably halt execution in SDK mode, even when they detect a dangerous operation that has already completed. Distinct from the Stop hook issue (#40022) which affects resumed sessions specifically. No workaround in SDK mode. See #29991.
Marketplace plugins that declare hooks using the documented string-path form ("hooks": "./hooks/hooks.json") cause a TypeError on `/reload-plugins`: J?.reduce is not a function. The plugin loader expects hooks to be an array, not a string reference. This crashes the entire reload operation, not just the affected plugin. Affects any plugin (including enforce-hooks) that uses the string-path hooks format. Workaround: declare hooks inline as an array in the plugin manifest instead of referencing an external file. See #41943.
On Windows, bypassPermissions mode does not auto-approve Edit/Write when the working directory uses UNC paths (\\server\share\...). Every file operation prompts for confirmation despite bypass mode being active. The path normalization logic does not recognize UNC paths as "within the project directory." This compounds with #40328 (bypass partially broken) and #41763 (suspicious path downgrades bypass). PreToolUse hooks returning {"allow": true} work regardless of path format. See #41914.
PreToolUse hooks can approve, deny, or modify tool inputs, but cannot suppress Claude Code's built-in terminal rendering of tool results. A user building an external diff viewer over Unix domain sockets reviews Edit/Write diffs in a purpose-built TUI, but Claude Code still renders the full inline diff redundantly in the terminal. IDE integrations (VS Code, JetBrains) already suppress terminal diffs and redirect to their native diff viewers, but hooks have no equivalent suppressDiff or skipTerminalDiff output field. External review tooling built on hooks always produces duplicated display. No workaround. See #42014.
Before v2.1.89, PreToolUse and PostToolUse hooks sometimes received relative file_path values for Write, Edit, and Read tools, despite documentation stating paths would be absolute. This is now fixed. file-guard already handled both relative and absolute paths, but hooks that assumed absolute paths (e.g., checking prefixes like /etc/ or /home/) could silently miss relative path inputs on older versions. If you are on v2.1.88 or earlier, always resolve paths in your hooks before matching.
A new hook event fires after the auto mode classifier denies a tool call. Return {"hookSpecificOutput": {"retry": true}} to tell the model it can retry the denied operation. Without this hook, auto mode denials are final and not retried. This enables custom recovery logic: for example, a hook could log the denial, adjust parameters, or escalate to a human reviewer. enforce-hooks does not yet generate PermissionDenied hooks. safety-check detects when this event is configured. See #41261.
Before v2.1.89, when context refilled to the limit immediately after compaction, Claude Code would loop indefinitely burning API calls on repeated compaction cycles. v2.1.89 detects three consecutive refill-after-compact cycles and stops with an actionable error. This previously caused runaway costs in long sessions with large CLAUDE.md configs or verbose hook output. Stateful hooks that inject additionalContext on every tool call are a common trigger for context pressure that leads to thrash loops.
The original fix in v2.1.77 for hooks overriding deny rules (including enterprise managed settings) was incomplete or regressed. v2.1.89 re-fixes this. If you updated past v2.1.77 and still saw hooks overriding deny rules, update to v2.1.89.
On Windows, hook commands that reference sibling directories via .. (e.g., node "../other-repo/.claude/scripts/hooks.mjs") intermittently drop the `..` component, treating the target as a subdirectory instead of a sibling. Running the same command from bash in the same working directory resolves correctly. Affects all hook events (Stop, PreToolUse, PostToolUse). Quoting the path does not fix it. Workaround: use absolute paths in hook commands on Windows, or resolve paths in a wrapper script before invoking the hook. Related to #39478 (spaces in working directory) and #40084 (spaces in user profile path). See #42065.
Returning {"decision": "deny"} from a PermissionRequest hook does not suppress the permission prompt. The interactive dialog still appears regardless of the hook's output. PermissionRequest hooks cannot auto-deny dangerous commands; they can only auto-allow (which works). PreToolUse hooks are the reliable deny path. See #19298.
PermissionRequest hooks run asynchronously. If the hook takes more than ~1-2 seconds to return, the permission dialog appears anyway, even when the hook returns {"behavior": "allow"}. The dialog is added to UI state before awaiting hook results. Fast hooks (< 1s) work reliably; slow hooks (network calls, complex checks) race with the dialog. Breaks CI/CD workflows and security automation that depend on hooks intercepting prompts. See #12176.
When subagents spawned via Agent Teams need permission, the request is delegated to the parent session's terminal prompt without triggering PermissionRequest hooks. Main-session requests fire correctly. Notification hooks (Telegram, Slack) and auto-approval hooks are bypassed for all subagent permission requests. Compounds with #37730 (subagents don't inherit permission settings) and #40241 (bypass doesn't propagate). See #23983.
In the VS Code extension, PreToolUse hooks returning {"permissionDecision": "ask"} are silently ignored. The same hooks work correctly in CLI. Other decisions (allow, deny, block) work in both environments. Hooks cannot enforce "ask before executing" policies in VS Code; only hard-allow or hard-deny. Workaround: use "decision": "block" instead of "ask", at the cost of losing case-by-case approval. Compounds with #40029 (Stop hooks don't fire in VS Code). See #13339.
Claude Code may rewrite commands to avoid triggering hook patterns. If a PostToolUse hook flags a forbidden term, the model can vary its output to circumvent the check. This is not a platform bug but a fundamental limitation: any hook that relies on string matching against model output can be evaded by a sufficiently capable model rephrasing its response. Hooks that match on tool names or structural properties (file paths, exit codes) are more robust than those matching on content. See #29691.
Claude can use Edit/Write tools to modify hook scripts that are meant to constrain it. permissions.deny rules do not reliably prevent this (see #22055 for a regression). This is a circular security gap: the enforcement mechanism is editable by the entity being enforced. Workaround: set hook files to read-only via OS permissions (chmod 444), use managed settings for enterprise deployments, or place hooks outside the project directory where the model is less likely to discover them. See #11226, #22055.
The permissions system's pattern matching has 30+ open issues documenting failures: wildcards don't match compound commands, deny rules are bypassed via pipes, && chains, and reordered flags. This is not a collection of bugs but a structural limitation: the matching model operates on full command strings rather than parsed ASTs. Hooks solve this by running arbitrary code that can parse commands properly (e.g., bash-guard splits compound commands into segments). See #30519.
When a user types a slash command (e.g., /ce:work, /commit), PreToolUse hooks with "Skill" matcher do not fire. PostToolUse hooks do fire, but only after the action completes. Any hook-based enforcement that depends on blocking Skill tool calls before execution is bypassed by slash command invocations. Additionally, non-blocking hook output formats (systemMessage, decision:allow+reason, hookSpecificOutput) are silently discarded for this event type; only block decisions and stderr (exit 2) are delivered. See #42250.
The if property on hook entries (used to conditionally gate hook execution, e.g., "if": "Bash(*git *)") is silently removed whenever Claude Code rewrites settings.json via the /model command. Hook commands still fire, but without their conditional filters, they run on every tool call instead of only matching ones. This silently degrades performance and can cause unexpected blocks. Workaround: re-add if properties after changing models, or keep a backup of your settings.json. See #42225.
When the agent asks the user a yes/no gating question and a background task notification arrives before the user responds, the agent answers its own question as "yes" and proceeds without user consent. This is a race condition in the consent model that hooks cannot prevent, because the bypass happens at the conversation level before any tool call occurs. Affects workflows with background tasks (Agent tool, long-running Bash commands) where notifications can interrupt the permission flow. See #42236.
Hooks only fire around tool calls (PreToolUse, PostToolUse). When Claude asks the user a gating question like "Should I proceed?" or requests clarification, no hook event occurs. This means hooks cannot intercept, modify, or log agent-to-user prompts. In autonomous workflows, this gap means there is no programmatic way to detect when the agent is waiting for input vs. processing. Workaround: none currently. Monitor session logs for gaps in tool activity as a proxy for "agent waiting" state.
Hooks receive tool name, tool input, and session metadata, but no information about context window usage (tokens consumed, compression history, remaining budget). This means hooks cannot implement threshold-based actions like "save progress when context is 50% full" or "warn when approaching token limits." Workaround: track approximate token usage externally by summing tool inputs/outputs in session-log, though this is imprecise.
Setting env.PATH in settings.json to override the default PATH does not propagate to the Bash tool. Commands executed via Bash still use the system PATH, not the user-configured one. This affects workflows where tools like cargo, poetry, or custom binaries are installed in non-standard locations. Workaround: use wrapper scripts that source the correct environment, or set PATH in hook commands directly.
PostToolUse hooks configured in .claude/settings.json load correctly (visible via /hooks) but silently never execute in the Desktop App when tools like Edit are used. No error, no statusMessage, no command output. The hook simply never runs. This is a regression: the same hooks work in CLI. Compounds with #13339 (VS Code ignores ask decision) and #40029 (Stop hooks don't fire in VS Code). Workaround: use CLI instead of Desktop App for workflows that depend on PostToolUse hooks (formatters, type-checkers, audit logs). See #42336.
When a custom subagent (defined in .claude/agents/) runs a Bash command containing 2>&1, the Bash tool crashes with "Tool result missing due to internal error" inside the agent, surfacing as "Internal tools error during invocation." No output is returned, no approval prompt appears. This is on Windows/Git Bash. Workaround: prohibit 2>&1 in agent definitions and use separate stdout/stderr handling. See #42324.
When a plugin defines an agent type with tools: all in its frontmatter, the sub-agent's Write/Edit tool calls are silently blocked. The agent reports success, but nothing is written to disk. No error is returned. Using subagent_type: "general-purpose" with the same prompt works correctly. Hook-based enforcement cannot catch these tool calls because they are swallowed before reaching the hook layer. See #42333.
Rules like "write clean code," "use descriptive variable names," or "keep functions under 20 lines" have no tool-call signal to match against. The tool skips these and explains why during --scan.
When a subagent (Explore, Plan, or custom) completes and returns to the main agent, the terminal re-renders the full startup banner (robot icon, version info, working directory). This is visually identical to a crash recovery or session restart. For hook-based workflows that monitor session state, this false restart signal can trigger unnecessary re-initialization.
On Windows 11, Claude Code's TUI re-renders the entire visible conversation history every time a tool call completes. Response blocks appear multiple times on screen. For hook-intensive workflows with many sequential tool calls, this multiplies visual noise.
claude update.When both an npm-global and native installation of Claude Code coexist, claude update fails with "multiple installations found." This can happen when users install via npm install -g and later use the native installer. Affects hook users who need to stay on specific versions for hook compatibility. Workaround: remove one installation method before updating.
All hooks fire correctly at session start but silently stop working after approximately 2.5 hours. No errors are logged; hooks simply stop being invoked. All hook-based enforcement disappears mid-session without warning. Workaround: restart sessions before the 2.5-hour mark.
When a PreToolUse:Edit hook blocks file modifications, Claude switches to the Bash tool (e.g., echo "..." > file) to achieve the same edit. A single-tool hook is insufficient; pair file-guard (Edit/Write) with bash-guard (Bash) to cover both paths.
The permission mode changes from "Bypass permissions" to "Edit automatically" mid-session without user interaction. Write tool calls start prompting for permission, breaking autonomous workflows. Distinct from hook-triggered resets (#37745) and suspicious-path downgrades (#41763).
When a subagent spawned via the Agent tool completes and returns results, no Stop hook fires with the subagent's session_id. Other lifecycle hooks fire correctly. Session-tracking tools accumulate "ghost sessions" with no end event.
The plugin marketplace installer does not set execute permissions on .sh hook scripts. Auto-updates install all hook files as -rw-rw-r-- (no +x bit), causing hooks to fail on every session. Fix: chmod +x the affected scripts.
Despite explicit deny rules for CLAUDE.md in settings.json, Claude still modifies CLAUDE.md, especially during commits. When overwritten, the model loses its project context. Workaround: use file-guard to protect CLAUDE.md at the hook level, or set read-only with OS permissions.
Hooks defined in project and user settings.json fire for the main session but are silently skipped for teammates spawned via Agent tool with team_name. Hooks in agent frontmatter also do not fire. Teams relying on PreToolUse for role-based restrictions have no enforcement on teammates.
The env.PATH setting in ~/.claude/settings.json is not applied when Claude Code is launched from the macOS desktop app. PATH falls back to /usr/bin:/bin:/usr/sbin:/sbin, missing Homebrew and other user-installed binaries. Hook scripts that depend on jq, python3, or other tools in /opt/homebrew/bin will silently fail. Workaround: use absolute paths in hook scripts, or launch Claude Code from a terminal where PATH is correctly set.
Setting DISABLE_AUTO_COMPACT=1 and AUTOCOMPACT_PCT_OVERRIDE=95 in settings.json env does not prevent compaction. Sessions compact to 6% context on first tool call despite explicit disable. Affects stateful hooks and autonomous agents that rely on conversation history.
Experimental agent teams launch teammates via tmux send-keys, but the command is split at ~255 bytes. The second fragment fails as a standalone command, the agent never starts, and the parent reports success. Long project paths or agent names trigger it. Manual tmux send-keys does not reproduce.
Claude Code stores background task output in /private/tmp/claude-{UID}/ with no size limits, no TTL, and no cleanup. A single runaway task consumed 405 GB, silently filling the disk. Affects autonomous agents and heavy run_in_background users.
find command injection bypasses user approval prompt (CVE-2026-24887).Command parsing error allowed untrusted context content to trigger arbitrary command execution through find without the approval prompt firing. CVSS 7.7 HIGH. Fixed in v2.0.72. bash-guard catches dangerous find patterns regardless of version.
The deny rule parser has a hard cap of 50 subcommands per pipeline. Commands chaining 50+ subcommands (e.g., 50 no-ops then curl) fall through to "ask" instead of "deny." Reported by Adversa security firm. bash-guard is unaffected as it evaluates each segment independently.
When a PostToolUse hook reformats a file after Edit or Write (e.g., prettier, black, gofmt), the next Edit/Write to the same file fails with "File content has changed." The formatter changes the file hash between tool calls. Fixed in v2.1.90. On pre-v2.1.90, workaround: use a separate formatting step instead of a PostToolUse hook.
& bypasses tool permission checks (fixed v2.1.90).Appending & to a PowerShell command launched it as a background job, bypassing tool permission evaluation. Fixed in v2.1.90 alongside three other PowerShell hardening fixes: -ErrorAction Break debugger hang, archive-extraction TOCTOU, and parse-failure fallback degradation. Pre-v2.1.90 PowerShell permission checks have multiple bypass vectors.
When PowerShell command parsing fails (malformed syntax, unusual quoting, encoding tricks), deny rules fall through to a weaker fallback evaluation instead of denying by default. Combined with the trailing & bypass and archive-extraction TOCTOU, pre-v2.1.90 PowerShell tool permission checks have significant gaps. bash-guard and safety-check handle Bash/sh but not PowerShell.
When a skill defines Stop hooks in its SKILL.md file, they are never invoked when the skill session ends. Start hooks and tool hooks work, but the Stop lifecycle event is silently skipped. Workaround: have skill instructions tell Claude to run the stop script manually before exiting.
Async hook events (especially SubagentStart/SubagentStop) generate "Async hook completed" messages in the conversation transcript on every invocation. There is no setting to suppress or filter these messages. Heavy hook usage floods the conversation with noise, degrading the user experience and the model's effective context. Originally filed as #9603, auto-closed and re-filed.
permissions.allow and permissions.deny rules for Bash commands in settings.json are not reliably enforced. Denied commands may still execute, and allowed commands may still prompt for approval. Users must write custom PreToolUse hooks as a workaround. This is exactly the gap bash-guard fills.
In the VS Code/Cursor extension, commands like rm execute without permission prompts even when not in permissions.allow, and "Allow for all projects" does not persist to settings.json, causing repeated prompts. Reported on v2.1.78. The CLI does not have this problem. Extension users relying on the permission system have no enforcement.
When Opus 4.6 1M is selected, the auto-mode safety classifier sends requests to claude-sonnet-4-6[1m] instead of the correct suffix. If Sonnet 1M is not available in the user's API plan, Bash and other execution tools fail entirely. Not hookable; the classifier runs before any tool call reaches hooks.
When the Sonnet classifier powering auto-mode safety is unavailable (API errors), all execution tools are blocked and only read-only tools work. This creates a complete enforcement outage. Not hookable; the classifier failure happens at the permission layer before hooks fire.
Hooks that exit 0 with no stderr and valid JSON on stdout can still be labeled as "Hook Error" in the transcript. Claude interprets the false error as a real failure and stops working mid-turn. A functioning enforcement hook can be treated as broken by the model.
The Task tool was renamed to Agent in v2.1.63, but this was an undocumented breaking change. Existing hooks matching on tool_name === "Task" silently stopped working. The hook payload now reports the tool as Agent with no migration path or deprecation warning.
$() subshells and parentheses in arguments.The if-condition pattern matcher in hooks and the permission allow/deny wildcard matcher both fail when Bash commands contain $() subshells or parentheses in arguments. Commands like echo $(date) or gcloud logging read 'filter=(severity=ERROR)' incorrectly trigger blocking hooks or fail to match allow rules. The parser appears to default to "match" (fire the hook / prompt for permission) on parse failure, making this a false-positive issue. Compound constructs like pipes and heredocs also break matching. Workaround: use PreToolUse hooks that parse the full command string instead of relying on if-condition patterns. bash-guard does this.
Human: turns in long agent-team sessions.In long conversations with many subagents (Agent Teams with 10+ teammates), Claude repeatedly generates output that appears as user-authored Human: turns in the conversation UI. The user did not write these messages. This is an integrity violation: fabricated user input is indistinguishable from real input. Occurs after multiple context compactions in sessions with heavy subagent usage. No known workaround other than starting shorter sessions.
/reload-plugins in existing session.After running /reload-plugins mid-session, plugin skills from the skills/ directory are listed in the system-reminder but cannot be invoked. Slash commands resolve to deprecated command stubs instead of the registered skills. No combination of /reload-plugins, /plugin enable/disable, or fully qualified skill names fixes it within the session. Starting a new session is the only workaround.
/tmp is full.When /tmp has no free disk space, all Bash tool invocations fail with a generic Exit code 1 regardless of the command. There is no indication that the failure is caused by insufficient disk space. This affects any workflow that depends on the Bash tool, including hook scripts that shell out. Workaround: monitor /tmp usage and clear space before running Claude Code.
AskUserQuestion fires.The Notification hook event does not fire in Plan Mode when Claude calls AskUserQuestion to prompt user input. The Stop hook fires correctly in Plan Mode, but Notification hooks are silently skipped for elicitation events. Tested with matcher: "*", "idle_prompt", and "elicitation_dialog" on Windows. Users building notification systems will miss prompts during Plan Mode. No workaround.
autoaccept-edits during long sessions.During long sessions (600+ API calls, 3+ hours), bypass permissions mode can silently switch to autoaccept-edits without user action. Correlates with Write/Edit operations on files outside the project root (~/.claude/, other drives). Observed 5 times in one session on Windows. The user must manually switch back, but the downgrade can recur. Only starting a new session fully resolves it. Affects automated workflows that depend on consistent permission enforcement.
Temp dirs are namespaced by <uid>/<project-path-hash> but not by session ID. When multiple Claude Code sessions target the same project directory, each session's startup cleanup can delete output files another active session is writing to or reading from, causing ENOENT errors on task output. Common when running parallel agents, background tasks, or multiple terminal tabs. Session ID is not part of the namespace. Workaround: use separate project directories for parallel sessions.
When a script executed via the Bash tool spawns background subprocesses and registers an EXIT trap for cleanup, the trap is not triggered when the Bash tool terminates the process. Background children become orphaned and accumulate. Affects hooks and scripts that use nohup/disown patterns (e.g., the SessionEnd workaround for detaching heavy work). The Bash tool appears to kill only the direct child, not the process group.
When a deny rule blocks a specific command (e.g., Bash(rm *)), the model uses alternative tools to accomplish the same goal: python3 -c "import os; os.remove('file')" when rm is denied, or Node.js fs.unlinkSync(), Ruby File.delete(), Perl unlink(). The model treats permission blocks as "tool blocked" not "goal blocked" and pivots to equivalent operations in other languages. Deny rules and hooks that match specific commands are fundamentally limited by this: blocking rm requires also blocking every other deletion vector. Mitigation: write hooks that match the goal (file deletion, network access) across all interpreters, not just the specific binary. Related to #38841 (state file manipulation) and #40117 (--no-verify bypass with multiple tactics).
bypassPermissions does not suppress multi-line Bash description safety check.The --dangerously-skip-permissions flag and bypassPermissions permission mode do not suppress Claude Code's built-in multi-line Bash command safety check. Users in bypass mode still get prompted with a confirmation dialog when commands contain newlines. This breaks automated workflows and headless -p scripts that expect bypass mode to suppress all prompts. The safety check fires independently of the permission system.
bypassPermissions for file creation.When defaultMode is set to bypassPermissions in user settings, sub-agents spawned via the Agent tool still prompt for file creation confirmation (Write tool). The parent session correctly operates in bypass mode, but the permission mode does not fully propagate to sub-agents for all tool types. Distinct from #25000 (deny-rule bypass) — here the sub-agent is more restrictive than intended, not less.
.claude/rules/ rules have no enforcement mechanism.Rules defined in CLAUDE.md, .claude/rules/, and memory files are read by the model but have no runtime enforcement. The model can read these rules and still violate them during execution. Bold text, capitalization, "MANDATORY" labels, and explicit consequence statements do not change this — they are all prompt content with no binding force. This is the core problem that hook-based enforcement exists to solve.
CLAUDE_PLUGIN_ROOT env var not always set when invoking plugin hooks.Plugin hooks registered in ~/.claude/settings.json that reference ${CLAUDE_PLUGIN_ROOT} intermittently fail with MODULE_NOT_FOUND because the environment variable is not always set by the Claude Code runtime. This is distinct from the path-spaces issue (#40084): the variable is entirely absent, not malformed. Affects plugin-installed hooks that rely on this variable for script paths. Workaround: use absolute paths in hook commands instead of ${CLAUDE_PLUGIN_ROOT}.
apiKeyHelper in project-level settings enables arbitrary code execution on open.The apiKeyHelper field in .claude/settings.json is executed as a shell command via execa with shell: true. Since this file can be committed to a repository, cloning and opening Claude Code anywhere in the project runs the command without user consent. In CI/CD pipelines using claude -p, the trust dialog is bypassed entirely, making this a supply-chain attack vector. Proposed fix: restrict apiKeyHelper to user-level config only, or require explicit confirmation before execution.
allowManagedHooksOnly blocks plugin hooks from trusted marketplaces.Organizations using allowManagedHooksOnly: true block all non-managed hooks, including those shipped by vetted plugins from known marketplaces. There is no granular setting like allowPluginHooksFromKnownMarketplaces to permit plugin-supplied hooks while still restricting user-defined ones. This forces orgs to choose between full hook lockdown and allowing all hooks, with no middle ground for plugin trust.
When a plugin hook reads OAuth credentials from the macOS Keychain and performs a token refresh (e.g. POST /v1/oauth/token), it can invalidate the access token that Claude Code is currently using. The main session then fails authentication on its next API call with no indication that a hook caused the failure. Hooks and the main session share credential state without coordination.
There are no TeamCreated or TeamDeleted hook events. Platforms that orchestrate Claude Code Agent Teams cannot detect when a team is created or deleted to synchronize state with external systems (dashboards, billing, audit logs). The only workaround is polling the Teams API.
bypassPermissions broken on UNC paths in VS Code (Windows regression).Setting defaultMode: "bypassPermissions" in ~/.claude/settings.json no longer suppresses write/edit permission prompts when the working directory is a UNC path (e.g. \\server\share\...). This is a regression introduced after v2.1.69; mapped drive letters still work correctly. The same issue affects acceptEdits mode on UNC paths (never worked).
--dangerously-skip-permissions still prompts for Edit/Write confirmations.On v2.1.90, running with --dangerously-skip-permissions plus "defaultMode": "bypassPermissions" in project settings and "skipDangerousModePermissionPrompt": true in user settings still shows Edit/Write confirmation prompts on every edit. The only workaround is selecting "Yes, allow all edits during this session" at session start. Distinct from #40014 (settings-only): here the CLI flag itself does not fully bypass.
When multiple PreToolUse hooks match the same tool (e.g. both a project hook and a plugin hook match Edit), only one hook receives the stdin JSON payload. Other matching hooks get empty stdin, causing them to silently exit 0 (allow) instead of executing their guard logic. This effectively bypasses any hook that loses the stdin race. Distinct from #38162 (async-specific): this affects synchronous hooks when multiple match.
bypassPermissions still prompts on .git/ and .claude/ paths.With bypassPermissions mode active and explicit Bash(*), Edit(*) wildcards in the allow list, operations on .git/ paths intermittently prompt for permission (same commands work earlier in the session), and operations on .claude/skills/ paths consistently prompt. Reported on Linux/VS Code. Distinct from #42611 (UNC paths on Windows).
bypassPermissions not restored on session resume (VS Code).When bypassPermissions is configured via initialPermissionMode in VS Code settings, resumed conversations revert to default permission mode and prompt for every edit. New sessions may pick it up, but resumed sessions consistently fail. Hooks that depend on the session running in bypass mode cannot rely on it persisting across resume.
Using isolation: "worktree" on the Agent tool inside a git submodule creates the worktree in .git/modules/<path>/.claude/worktrees/ instead of the project's own .claude/worktrees/. This places the agent outside the project's permission scope, causing bypassPermissions to be silently downgraded and triggering unexpected permission prompts.
During a git history scrub task, the agent disabled branch protection rules, deleted a repository ruleset, and force-pushed without asking the user, despite system instructions requiring confirmation for actions that "affect shared systems beyond your local environment." The agent used gh api to PUT allow_force_pushes, PATCH the ruleset to disabled, and DELETE the protection rule entirely. This bypasses the permission system because gh api calls are ordinary Bash commands with no special safety treatment. Workaround: use bash-guard (v0.12.0+) which blocks gh api mutations on /protection and /rulesets endpoints.
// in code comments.The built-in UNC-path-detection hook in PreToolUse:Edit falsely blocks edits containing // in PHP, JavaScript, or C++ comments. The check (v.includes('//') && !v.includes('://')) is too broad: it matches any double-slash, not just UNC paths. This causes legitimate edits to files with comment syntax to be rejected. Affects WSL users most visibly but the logic is platform-independent. Workaround: none within the hook system; the false positive is in Claude Code's own built-in hook, not user-configurable.
Even with dangerouslySkipPermissions or bypass mode enabled, Claude may still stop and prompt for user input instead of proceeding autonomously (v2.1.91). This breaks autonomous pipelines and agent loops that depend on non-interactive execution. Workaround: none known; the session must be manually resumed.
The built-in brace expansion security check falsely triggers on Bash commands containing single-quoted JSON with multiple comma-separated values. Short JSON payloads pass; longer ones trigger a "Brace expansion" permission prompt even though shell brace expansion cannot occur inside single quotes. This affects automated workflows and CI pipelines that pass JSON via CLI arguments. Workaround: pipe JSON from a file or heredoc instead of inline arguments.
When Claude Code prompts for permission to edit a file it classifies as "sensitive" (e.g., paths under ~/.claude/), selecting "Yes, and always allow access to [path] from this project" does not persist the exception. The same prompt reappears in every new session for the same file paths. Distinct from the directory-access persistence bug (#40606/#35787) and the hardcoded sensitive-file prompt (#41615): here the "always allow" option exists but its state is not saved. Not hookable — the prompt fires before any tool hook. Workaround: none known; users must re-approve every session.
On Windows with non-ASCII session content (e.g., Korean text), Stop hooks fail with garbled UTF-8 output. The PowerShell encoding pipeline corrupts multi-byte characters, producing mojibake in hook stderr. The hook still runs but reports a non-blocking error. Affects any Stop hook on Windows when the session contains non-Latin characters.
When running with --dangerously-skip-permissions, toggling plan mode via /plan only activates correctly on the first use within a session. From the second invocation onward, plan mode is ignored and Claude executes actions directly without planning. Labeled as regression. Affects autonomous workflows that alternate between plan and execute phases.
Combining --continue with -p silently creates a new session instead of continuing the most recent one. No error or warning is emitted. Root cause: a 2.1.90 change excluded sessions created by claude -p or SDK from the --resume picker, which also broke --continue session lookup. Labeled as regression. Affects any automation or scripting that chains prompts across sessions using --continue -p.
In v2.1.91, --resume and /resume silently load 0% of conversation history. Three regressions compound: (1) new synchronous reader skips fork pruning for files >5 MB, (2) new timestamp fallback bridges across fork boundaries connecting messages from different forks, (3) getLastSessionLog uses findLatestMessage without leafUuids check, picking synthetic messages from resume attempts. Each failed resume further corrupts the session JSONL by forking the parentUuid DAG. Labeled data-loss. Reporter documented $364 in extra API costs in a single day.
When an admin sets denyRead or denyWrite on a path in managed settings, a user can override it by adding the same path to allowRead or allowWrite in their own settings.json. The allow rule wins over the deny rule on exact path matches regardless of settings scope. allowManagedReadPathsOnly exists as a workaround but adds friction. Reporter notes this never worked (not a regression). Labeled as security issue.
The statusline command (configured in settings.json for HUD display) occasionally receives raw hook stdout instead of Claude Code's structured JSON metadata (model, context_window, rate_limits). Hook output is incorrectly routed to the statusline command's stdin instead of being routed exclusively to the hook result parser. Causes raw JSON or truncated text in the statusline display. Affects v2.1.91 when using statusline with hooks that return output.
bash -c wrapping bypasses .claude/ directory write protection.The permission system protects .claude/ files from modification (edit, write, direct bash commands all trigger a confirmation modal). But wrapping the command in bash -c 'echo "..." >> .claude/file' bypasses the check entirely: no modal, write succeeds silently. The pattern matching inspects the top-level command string but not nested subshells. A model or prompt injection could modify hooks, settings.json, skills, or agent definitions without user confirmation. file-guard catches this if .claude/ is in the .file-guard config, because it scans the full Bash command string for protected paths including inside quoted arguments.
.claude/agents/.When multiple custom agent files are placed in .claude/agents/, only the alphabetically first file is loaded. All others are silently ignored. Renaming a file to be alphabetically earlier causes it to replace the previously shown agent. No error or warning is displayed. Workaround: use a single agent file or ensure the most important agent is alphabetically first.
/tmp/claude allowlisted but /tmp is not writable.The sandbox sets TMPDIR=/tmp/claude and allowlists writes to /tmp/claude, but if /tmp/claude does not exist, creating it requires writing to /tmp which the sandbox blocks. This chicken-and-egg problem affects hooks and tools that need temporary files, particularly on WSL after a reboot. Workaround: manually create /tmp/claude before starting Claude Code.
If a worktree is deleted while a Claude Code remote-control session is running inside it, the session terminates and remote-control becomes permanently broken for that project. No recovery path works: pruning worktrees, deleting .claude/, clearing session state all fail. Only affects --spawn worktree mode. Workaround: avoid deleting worktrees while remote-control sessions are active inside them.
Custom skills defined in .claude/skills/ intermittently become unavailable during a session, returning “Unknown skill” errors. All project skills disappear simultaneously. Restarting Claude Code resolves the issue. Not related to compaction or context window capacity. Workaround: restart Claude Code to restore skill availability.
PostToolUse hooks configured in .claude/settings.json load correctly but do not trigger when tools are used in the Claude Code Desktop App. No error messages are shown; the hook simply does not fire. The same hooks work when run manually in a terminal. Reported as a regression. Affects any hook-based workflow (formatting, type-checking, file-guard, etc.) when using the Desktop App instead of CLI. Workaround: use Claude Code CLI instead of the Desktop App.
When a Stop hook writes OSC escape sequences (tab title via OSC 2, background color via OSC 11) to /dev/tty, Claude Code's own rendering immediately overwrites them. The hook fires and the write lands, but CC clobbers the output within milliseconds. This makes it impossible to build terminal tab indicators that reflect session state. Additionally, there is a 5-15 second gap between user prompt submission and first PreToolUse event where no hook fires during initial thinking time.
.claude-personal profile (subscription/OAuth sessions).MCP servers configured via claude mcp add are not loaded in interactive sessions when using the .claude-personal profile (personal subscription / OAuth auth). claude mcp list shows servers as connected, but /mcp inside the session says “No MCP servers configured.” Servers added to every config location are ignored. The same servers work correctly in the API key profile (~/.claude/).
Deny rules in .claude/settings.local.json (e.g. Bash(git *)) are not inherited by subagents launched via the Agent tool. A subagent can execute git checkout or git restore, reverting files and destroying uncommitted work, even though the parent session has an explicit deny rule. This extends the known pattern that subagents do not fully inherit permission settings. Workaround: add deny rules to .claude/settings.json (project-level, shared) instead of settings.local.json, or use managed-settings.json for organization-wide enforcement.
A SessionStart hook that spawns a background process (e.g. caffeinate -s &) causes Claude Code to hang indefinitely in the Desktop App after v2.1.87. The background process inherits stdin/stdout file descriptors used for stream-json IPC, so the parent blocks waiting for pipe EOF. This was tolerated in earlier versions but became fatal after v2.1.87 tightened subprocess communication. Workaround: redirect background process I/O explicitly (e.g. caffeinate -s </dev/null >/dev/null 2>&1 &).
When editing files in ~/.claude/ directory, selecting “Allow for Session” does not persist the permission. Claude Code re-prompts for the same permission on subsequent tool calls within the same session. Reported on macOS with Bedrock API (Sonnet 4.5). This breaks autonomous workflows that need to modify Claude Code configuration files. Workaround: add explicit allow rules in settings.json for the specific file paths.
The Bash tool shell snapshot mechanism writes a hardcoded export PATH=... derived from the launch-time process environment, not from the user shell config. User-level PATH additions (including ~/.local/bin where the installer places the binary) are silently dropped. This causes spurious startup warnings and can make hooks fail silently if they depend on commands in user-added PATH directories. Workaround: none within hooks; the snapshot behavior is internal to Claude Code.
When a user approves a skill, the approval is not anchored to the skill file’s content hash. If the file is modified after approval (even mid-session), the modified version executes without re-prompting. Additionally, approving a skill can bypass tool-level deny rules in settings.json. This is a supply chain risk: anything with write access to ~/.claude/skills/ can escalate capabilities post-approval.
When a stdio-type MCP server process dies or disconnects, Claude Code marks it as failed and never attempts reconnection. HTTP/SSE/WebSocket servers get automatic reconnection with exponential backoff (5 attempts), but stdio servers are explicitly excluded. Users must manually run /mcp to reconnect. This affects any MCP integration that uses stdio transport (the most common local MCP pattern).
After completing one plan-approve-implement cycle, entering plan mode again for a new task does not reliably enforce read-only restrictions. Claude carries over the “approved” mental state and begins editing files before the user approves the new plan. Hooks that rely on plan mode as a safety boundary cannot trust it across multiple cycles in the same session.
Deny rules in the managed settings file (/Library/Application Support/ClaudeCode/managed-settings.json) are silently ignored. The same rules work correctly in ~/.claude/settings.json. This breaks the enterprise/MDM enforcement path: organization-level security policies deployed via managed settings have no effect.
When a PreToolUse hook blocks a tool call and returns a detailed error message with fix instructions, the model does not incorporate the feedback into its retry. Instead it apologizes and resubmits the same blocked command in a loop. This undermines enforcement hooks that guide the model toward correct behavior rather than just blocking.
JSONL session logs record tool_use and tool_result events but do not distinguish between tool calls auto-allowed by settings.json rules and those where the user was prompted. Audit scripts cannot identify which calls triggered permission prompts, making data-driven allow-list recommendations impossible.
On immutable-filesystem Linux distributions (Fedora Silverblue), bwrap fails because it cannot mkdir /usr/local/bin. Setting sandbox.enabled: false and CLAUDE_CODE_DISABLE_SANDBOX=1 both fail to actually disable the sandbox. Users on immutable-FS distributions cannot use Claude Code at all.
There is no mechanism to declare MCP tools as preferred over built-in tools. Tool description hints in MCP servers compete with built-in system prompt instructions and almost always lose. This forces MCP tool authors to rely on fragile prompt engineering rather than explicit priority configuration.
Cowork's request_cowork_directory uses statfs() f_type detection to reject all virtual and cloud-based filesystems (iCloud Drive, Dropbox, Google Drive, OneDrive, NFS, SMB). It does not check whether the mount is actually writable via ST_RDONLY or host-side ACLs. Users with code on cloud-synced or network-mounted directories cannot use cowork, even when the mount has full read-write access. Labeled as a regression. Affects both macOS and Windows.
When the model spawns a subagent (e.g., statusline-setup), that subagent can spawn further subagents with no enforced depth limit or token budget cap. A single simple task consumed 30% of a 5-hour rate limit through uncontrolled nested spawning. The parent agent has no visibility into subagent token consumption and no mechanism to abort runaway chains.
When auto-compact triggers and the user cancels then resumes the session, the reported context usage drops dramatically (e.g., 85% to 17%). The compact+resume path does not correctly reconcile subagent context contributions with the main conversation window.
When ~/.claude/settings.json is modified during an active streaming API call, the ConfigChange handler unconditionally clears network caches. This kills in-flight Bedrock streams through custom CA agents. Any tool or hook that writes to settings.json can silently break ongoing API calls.
When cycling through permission modes using Shift+Tab in the status bar, the dontAsk mode gets permanently dropped from the rotation after leaving it. Users cannot return to dontAsk mode via keyboard cycling and must restart to re-enter it.
When a parent agent sends a message via SendMessage, if the subagent completes before processing the queued message, the message is silently dropped. No error is returned to the parent. This breaks coordination patterns where agents need to communicate to in-flight subagents.
MCP_TIMEOUT does not control MCP server connection timeout. The MCP Client from @modelcontextprotocol/sdk is instantiated without passing requestTimeout, defaulting to 60 seconds. This inner timeout fires before the outer MCP_TIMEOUT wrapper. MCP servers needing longer than 60s to initialize are always marked as failed.
Plugin-based MCP tools (e.g. mcp__linear__*) hang for ~2 minutes with no response, no timeout, no error message, and no permission prompt, even when the tool pattern is explicitly in the permissions.allow list. Compounds with #280: the MCP_TIMEOUT env var does not help because the SDK overrides it.
The main agent spawned by a scheduled task (trigger) does not have access to MCP tools or connectors. Only sub-agents spawned by the main agent can use them. Workaround: have the scheduled agent immediately spawn a sub-agent for MCP-dependent work. Hooks attached to MCP tools will not fire for the top-level scheduled agent.
When encountering a CAPTCHA during a web task, Claude Code autonomously builds and tests a CAPTCHA solver against the live system without asking the user for permission. The model decides to bypass access controls on its own. PreToolUse hooks on the Bash tool are the only mitigation, as the model does not self-limit.
When opening a new project directory, Claude Code writes an empty mcpServers object to ~/.claude.json for that project. This overrides globally configured MCP servers. Users who set up MCP servers globally find them silently disabled in new projects because the per-project empty object takes precedence.
The VS Code extension does not respect defaultMode: bypassPermissions set in settings.json, even when configured at user, project, and local levels. The extension still prompts for every Bash command. The CLI respects this setting. Distinct from #215 which covers allow/deny rule enforcement.
Git Bash detection is broken on Windows for versions after v2.1.69, including the native binary. Claude Code fails to detect Git Bash as the shell environment, causing Bash tool execution failures. This is a regression distinct from earlier Git Bash issues (#8674, #10152, #13184, #31060). Affects all Windows users who rely on Git Bash instead of WSL.
Claude Code crashes with ENOENT: no such file or directory, posix_spawn 'pgrep' during normal Read tool operations on macOS. Bun's subprocess spawning uses a restricted PATH that does not include /usr/bin. The crash dumps minified ink UI source to the terminal. Regression in v2.1.91.
When using the Claude in Chrome MCP extension, only the first domain navigation triggers a permission prompt. All subsequent navigations to new domains are silently blocked with "Navigation to this domain is not allowed" without showing a prompt. Creating new tabs or retrying does not help. Multi-site workflows are impossible in a single session. Workaround: start a new session for each domain.
Plugin-defined hooks reference ${CLAUDE_PLUGIN_ROOT} to locate their scripts, but the variable resolves to an empty string at hook execution time. All three hook event types (SessionStart, UserPromptSubmit, PostToolUse) silently fail because the script path is wrong. Plugin hooks are effectively non-functional until this is fixed.
When already in acceptEdits mode, writes to the .claude/ directory produce a PermissionRequest with an empty permission_suggestions array. The addRules suggestions that previously existed are stripped. Users cannot grant scoped permissions for this directory through the normal prompt flow.
On Linux, the apply-seccomp sandbox filter binary loses its execute permission after auto-update. All Bash tool calls fail with exit code 126 until manually fixed with chmod +x. The sandbox becomes non-functional, effectively disabling all command execution. This is a regression.
An HTTP-type MCP server (e.g. vibe-annotations on 127.0.0.1) causes Claude Code sessions to close/crash when the agent reads from it. Happens consistently with multiple concurrent sessions open. No graceful error handling; the session just dies.
MCP connectors (Notion, Supabase, etc.) configured on Remote Triggers are not injected into the CCR session runtime. Connectors show as connected in trigger config and claude.ai settings, but ToolSearch finds nothing. Agent falls back to degraded mode.
A PreToolUse hook that returns exit code 2 with permissionDecision: "deny" is supposed to block the tool call but doesn't. The platform ignores the deny decision and proceeds with execution. The hook script runs (side effects occur), but the enforcement action is silently dropped. This undermines the core enforcement mechanism for hooks. Confirmed with repro.
When WebSearch is configured in the ask permission list in settings.local.json, web searches execute without prompting the user for approval. The ask mode is silently ignored for this tool, effectively making it always-allow.
The Edit tool always shows a diff-and-approve prompt even when three bypass mechanisms are active simultaneously: --dangerously-skip-permissions CLI flag, defaultMode: bypassPermissions in both global and project settings, and selecting allow-all-edits at the prompt. Each Edit call still prompts. Confirmed on WSL with repro.
When a plugin from a local symlink-based marketplace is registered in settings.json under enabledPlugins with value true, Claude Code suppresses the confirmation dialog but silently fails to start the MCP server. The plugin appears enabled but provides no tools at runtime. Does not affect GitHub-sourced marketplaces.
Scheduled triggers can become orphaned: the quota slot is consumed (trigger_limit_reached) but the Scheduled page shows no triggers and no option to delete or recreate. The old trigger ID exists server-side but is invisible in the UI. Users cannot reclaim the quota without support intervention.
Plugin notification channels (e.g., notifications/claude/channel in the Discord plugin) deliver events correctly in the first session but silently stop in all subsequent sessions. MCP tools continue to work; only the notification subscription fails to survive session boundaries.
The CLAUDE_ENV_FILE mechanism relies on shell evaluation semantics (file sourcing) and is currently broken. Environment variables set in one child process do not persist to the next. There is no declarative, shell-independent way to overlay environment variables on spawned processes. Hooks needing consistent env vars must set them internally per invocation.