Skip to main content

CLI commands

You can start sessions, pipe content, resume conversations, and manage updates with these commands:

Command Description Example
claude Start interactive session claude
claude "query" Start interactive session with initial prompt claude "explain this project"
claude -p "query" Query via SDK, then exit claude -p "explain this function"
cat file | claude -p "query" Process piped content cat logs.txt | claude -p "explain"
claude -c Continue most recent conversation in current directory claude -c
claude -c -p "query" Continue via SDK claude -c -p "Check for type errors"
claude -r "<session>" "query" Resume session by ID or name claude -r "auth-refactor" "Finish this PR"
claude update Update to latest version claude update
claude auth login Sign in to your Anthropic account. Use --email to pre-fill your email address, --sso to force SSO authentication, and --console to sign in with Anthropic Console for API usage billing instead of a Claude subscription claude auth login --console
claude auth logout Log out from your Anthropic account claude auth logout
claude auth status Show authentication status as JSON. Use --text for human-readable output. Exits with code 0 if logged in, 1 if not claude auth status
claude agents List all configured subagents, grouped by source claude agents
claude auto-mode defaults Print the built-in auto mode classifier rules as JSON. Use claude auto-mode config to see your effective config with settings applied claude auto-mode defaults > rules.json
claude mcp Configure Model Context Protocol (MCP) servers See the Claude Code MCP documentation.
claude plugin Manage Claude Code plugins. Alias: claude plugins. See plugin reference for subcommands claude plugin install code-review@claude-plugins-official
claude remote-control Start a Remote Control server to control Claude Code from Claude.ai or the Claude app. Runs in server mode (no local interactive session). See Server mode flags claude remote-control --name "My Project"
claude setup-token Generate a long-lived OAuth token for CI and scripts. Prints the token to the terminal without saving it. Requires a Claude subscription. See Generate a long-lived token claude setup-token

CLI flags

Customize Claude Code's behavior with these command-line flags. claude --help does not list every flag, so a flag's absence from --help does not mean it is unavailable.

Flag Description Example
--add-dir Add additional working directories for Claude to read and edit files. Grants file access; most .claude/ configuration is not discovered from these directories. Validates each path exists as a directory claude --add-dir ../apps ../lib
--agent Specify an agent for the current session (overrides the agent setting) claude --agent my-custom-agent
--agents Define custom subagents dynamically via JSON. Uses the same field names as subagent frontmatter, plus a prompt field for the agent's instructions claude --agents '{"reviewer":{"description":"Reviews code","prompt":"You are a code reviewer"}}'
--allow-dangerously-skip-permissions Add bypassPermissions to the Shift+Tab mode cycle without starting in it. Lets you begin in a different mode like plan and switch to bypassPermissions later. See permission modes claude --permission-mode plan --allow-dangerously-skip-permissions
--allowedTools Tools that execute without prompting for permission. See permission rule syntax for pattern matching. To restrict which tools are available, use --tools instead "Bash(git log *)" "Bash(git diff *)" "Read"
--append-system-prompt Append custom text to the end of the default system prompt claude --append-system-prompt "Always use TypeScript"
--append-system-prompt-file Load additional system prompt text from a file and append to the default prompt claude --append-system-prompt-file ./extra-rules.txt
--bare Minimal mode: skip auto-discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md so scripted calls start faster. Claude has access to Bash, file read, and file edit tools. Sets CLAUDE_CODE_SIMPLE. See bare mode claude --bare -p "query"
--betas Beta headers to include in API requests (API key users only) claude --betas interleaved-thinking
--channels (Research preview) MCP servers whose channel notifications Claude should listen for in this session. Space-separated list of plugin:<name>@<marketplace> entries. Requires Claude.ai authentication claude --channels plugin:my-notifier@my-marketplace
--chrome Enable Chrome browser integration for web automation and testing claude --chrome
--continue, -c Load the most recent conversation in the current directory claude --continue
--dangerously-load-development-channels Enable channels that are not on the approved allowlist, for local development. Accepts plugin:<name>@<marketplace> and server:<name> entries. Prompts for confirmation claude --dangerously-load-development-channels server:webhook
--dangerously-skip-permissions Skip permission prompts. Equivalent to --permission-mode bypassPermissions. See permission modes for what this does and does not skip claude --dangerously-skip-permissions
--debug Enable debug mode with optional category filtering (for example, "api,hooks" or "!statsig,!file") claude --debug "api,mcp"
--debug-file <path> Write debug logs to a specific file path. Implicitly enables debug mode. Takes precedence over CLAUDE_CODE_DEBUG_LOGS_DIR claude --debug-file /tmp/claude-debug.log
--disable-slash-commands Disable all skills and commands for this session claude --disable-slash-commands
--disallowedTools Tools that are removed from the model's context and cannot be used "Bash(git log *)" "Bash(git diff *)" "Edit"
--effort Set the effort level for the current session. Options: low, medium, high, max (Opus 4.6 only). Session-scoped and does not persist to settings claude --effort high
--exclude-dynamic-system-prompt-sections Move per-machine sections from the system prompt (working directory, environment info, memory paths, git status) into the first user message. Improves prompt-cache reuse across different users and machines running the same task. Only applies with the default system prompt; ignored when --system-prompt or --system-prompt-file is set. Use with -p for scripted, multi-user workloads claude -p --exclude-dynamic-system-prompt-sections "query"
--fallback-model Enable automatic fallback to specified model when default model is overloaded (print mode only) claude -p --fallback-model sonnet "query"
--fork-session When resuming, create a new session ID instead of reusing the original (use with --resume or --continue) claude --resume abc123 --fork-session
--from-pr Resume sessions linked to a specific GitHub PR. Accepts a PR number or URL. Sessions are automatically linked when created via gh pr create claude --from-pr 123
--ide Automatically connect to IDE on startup if exactly one valid IDE is available claude --ide
--init Run initialization hooks and start interactive mode claude --init
--init-only Run initialization hooks and exit (no interactive session) claude --init-only
--include-hook-events Include all hook lifecycle events in the output stream. Requires --output-format stream-json claude -p --output-format stream-json --include-hook-events "query"
--include-partial-messages Include partial streaming events in output. Requires --print and --output-format stream-json claude -p --output-format stream-json --include-partial-messages "query"
--input-format Specify input format for print mode (options: text, stream-json) claude -p --output-format json --input-format stream-json
--json-schema Get validated JSON output matching a JSON Schema after agent completes its workflow (print mode only, see structured outputs) claude -p --json-schema '{"type":"object","properties":{...}}' "query"
--maintenance Run maintenance hooks and start interactive mode claude --maintenance
--max-budget-usd Maximum dollar amount to spend on API calls before stopping (print mode only) claude -p --max-budget-usd 5.00 "query"
--max-turns Limit the number of agentic turns (print mode only). Exits with an error when the limit is reached. No limit by default claude -p --max-turns 3 "query"
--mcp-config Load MCP servers from JSON files or strings (space-separated) claude --mcp-config ./mcp.json
--model Sets the model for the current session with an alias for the latest model (sonnet or opus) or a model's full name claude --model claude-sonnet-4-6
--name, -n Set a display name for the session, shown in /resume and the terminal title. You can resume a named session with claude --resume <name>.

/rename changes the name mid-session and also shows it on the prompt bar
claude -n "my-feature-work"
--no-chrome Disable Chrome browser integration for this session claude --no-chrome
--no-session-persistence Disable session persistence so sessions are not saved to disk and cannot be resumed (print mode only) claude -p --no-session-persistence "query"
--output-format Specify output format for print mode (options: text, json, stream-json) claude -p "query" --output-format json
--enable-auto-mode Unlock auto mode in the Shift+Tab cycle. Requires a Team, Enterprise, or API plan and Claude Sonnet 4.6 or Opus 4.6 claude --enable-auto-mode
--permission-mode Begin in a specified permission mode. Accepts default, acceptEdits, plan, auto, dontAsk, or bypassPermissions. Overrides defaultMode from settings files claude --permission-mode plan
--permission-prompt-tool Specify an MCP tool to handle permission prompts in non-interactive mode claude -p --permission-prompt-tool mcp_auth_tool "query"
--plugin-dir Load plugins from a directory for this session only. Each flag takes one path. Repeat the flag for multiple directories: --plugin-dir A --plugin-dir B claude --plugin-dir ./my-plugins
--print, -p Print response without interactive mode (see Agent SDK documentation for programmatic usage details) claude -p "query"
--remote Create a new web session on claude.ai with the provided task description claude --remote "Fix the login bug"
--remote-control, --rc Start an interactive session with Remote Control enabled so you can also control it from claude.ai or the Claude app. Optionally pass a name for the session claude --remote-control "My Project"
--remote-control-session-name-prefix <prefix> Prefix for auto-generated Remote Control session names when no explicit name is set. Defaults to your machine's hostname, producing names like myhost-graceful-unicorn. Set CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX for the same effect claude remote-control --remote-control-session-name-prefix dev-box
--replay-user-messages Re-emit user messages from stdin back on stdout for acknowledgment. Requires --input-format stream-json and --output-format stream-json claude -p --input-format stream-json --output-format stream-json --replay-user-messages
--resume, -r Resume a specific session by ID or name, or show an interactive picker to choose a session claude --resume auth-refactor
--session-id Use a specific session ID for the conversation (must be a valid UUID) claude --session-id "550e8400-e29b-41d4-a716-446655440000"
--setting-sources Comma-separated list of setting sources to load (user, project, local) claude --setting-sources user,project
--settings Path to a settings JSON file or a JSON string to load additional settings from claude --settings ./settings.json
--strict-mcp-config Only use MCP servers from --mcp-config, ignoring all other MCP configurations claude --strict-mcp-config --mcp-config ./mcp.json
--system-prompt Replace the entire system prompt with custom text claude --system-prompt "You are a Python expert"
--system-prompt-file Load system prompt from a file, replacing the default prompt claude --system-prompt-file ./custom-prompt.txt
--teleport Resume a web session in your local terminal claude --teleport
--teammate-mode Set how agent team teammates display: auto (default), in-process, or tmux. See Choose a display mode claude --teammate-mode in-process
--tmux Create a tmux session for the worktree. Requires --worktree. Uses iTerm2 native panes when available; pass --tmux=classic for traditional tmux claude -w feature-auth --tmux
--tools Restrict which built-in tools Claude can use. Use "" to disable all, "default" for all, or tool names like "Bash,Edit,Read" claude --tools "Bash,Edit,Read"
--verbose Enable verbose logging, shows full turn-by-turn output claude --verbose
--version, -v Output the version number claude -v
--worktree, -w Start Claude in an isolated git worktree at <repo>/.claude/worktrees/<name>. If no name is given, one is auto-generated claude -w feature-auth

System prompt flags

Claude Code provides four flags for customizing the system prompt. All four work in both interactive and non-interactive modes.

Flag Behavior Example
--system-prompt Replaces the entire default prompt claude --system-prompt "You are a Python expert"
--system-prompt-file Replaces with file contents claude --system-prompt-file ./prompts/review.txt
--append-system-prompt Appends to the default prompt claude --append-system-prompt "Always use TypeScript"
--append-system-prompt-file Appends file contents to the default prompt claude --append-system-prompt-file ./style-rules.txt

--system-prompt and --system-prompt-file are mutually exclusive. The append flags can be combined with either replacement flag.

For most use cases, use an append flag. Appending preserves Claude Code's built-in capabilities while adding your requirements. Use a replacement flag only when you need complete control over the system prompt.

See also

Commands control Claude Code from inside a session. They provide a quick way to switch models, manage permissions, clear context, run a workflow, and more.

Type / to see every command available to you, or type / followed by letters to filter.

The table below lists all the commands included in Claude Code. Entries marked Skill are bundled skills. They use the same mechanism as skills you write yourself: a prompt handed to Claude, which Claude can also invoke automatically when relevant. Everything else is a built-in command whose behavior is coded into the CLI. To add your own commands, see skills.

Not every command appears for every user. Availability depends on your platform, plan, and environment. For example, /desktop only shows on macOS and Windows, and /upgrade only shows on Pro and Max plans.

In the table below, <arg> indicates a required argument and [arg] indicates an optional one.

Command Purpose
/add-dir <path> Add a working directory for file access during the current session. Most .claude/ configuration is not discovered from the added directory
/agents Manage agent configurations
/autofix-pr [prompt] Spawn a Claude Code on the web session that watches the current branch's PR and pushes fixes when CI fails or reviewers leave comments. Detects the open PR from your checked-out branch with gh pr view; to watch a different PR, check out its branch first. By default the remote session is told to fix every CI failure and review comment; pass a prompt to give it different instructions, for example /autofix-pr only fix lint and type errors. Requires the gh CLI and access to Claude Code on the web
/batch <instruction> Skill. Orchestrate large-scale changes across a codebase in parallel. Researches the codebase, decomposes the work into 5 to 30 independent units, and presents a plan. Once approved, spawns one background agent per unit in an isolated git worktree. Each agent implements its unit, runs tests, and opens a pull request. Requires a git repository. Example: /batch migrate src/ from Solid to React
/btw <question> Ask a quick side question without adding to the conversation
/chrome Configure Claude in Chrome settings
/claude-api Skill. Load Claude API reference material for your project's language (Python, TypeScript, Java, Go, Ruby, C#, PHP, or cURL) and Managed Agents reference. Covers tool use, streaming, batches, structured outputs, and common pitfalls. Also activates automatically when your code imports anthropic or @anthropic-ai/sdk
/clear Clear conversation history and free up context. Aliases: /reset, /new
/color [color|default] Set the prompt bar color for the current session. Available colors: red, blue, green, yellow, purple, orange, pink, cyan. Use default to reset
/compact [instructions] Compact conversation with optional focus instructions
/config Open the Settings interface to adjust theme, model, output style, and other preferences. Alias: /settings
/context Visualize current context usage as a colored grid. Shows optimization suggestions for context-heavy tools, memory bloat, and capacity warnings
/copy [N] Copy the last assistant response to clipboard. Pass a number N to copy the Nth-latest response: /copy 2 copies the second-to-last. When code blocks are present, shows an interactive picker to select individual blocks or the full response. Press w in the picker to write the selection to a file instead of the clipboard, which is useful over SSH
/cost Show token usage statistics. See cost tracking guide for subscription-specific details
/debug [description] Skill. Enable debug logging for the current session and troubleshoot issues by reading the session debug log. Debug logging is off by default unless you started with claude --debug, so running /debug mid-session starts capturing logs from that point forward. Optionally describe the issue to focus the analysis
/diff Open an interactive diff viewer showing uncommitted changes and per-turn diffs. Use left/right arrows to switch between the current git diff and individual Claude turns, and up/down to browse files
/doctor Diagnose and verify your Claude Code installation and settings. Results show with status icons. Press f to have Claude fix any reported issues
/effort [low|medium|high|max|auto] Set the model effort level. low, medium, and high persist across sessions. max applies to the current session only and requires Opus 4.6. auto resets to the model default. Without an argument, shows the current level. Takes effect immediately without waiting for the current response to finish
/exit Exit the CLI. Alias: /quit
/export [filename] Export the current conversation as plain text. With a filename, writes directly to that file. Without, opens a dialog to copy to clipboard or save to a file
/extra-usage Configure extra usage to keep working when rate limits are hit
/fast [on|off] Toggle fast mode on or off
/feedback [report] Submit feedback about Claude Code. Alias: /bug
/branch [name] Create a branch of the current conversation at this point. Alias: /fork
/help Show help and available commands
/hooks View hook configurations for tool events
/ide Manage IDE integrations and show status
/init Initialize project with a CLAUDE.md guide. Set CLAUDE_CODE_NEW_INIT=1 for an interactive flow that also walks through skills, hooks, and personal memory files
/insights Generate a report analyzing your Claude Code sessions, including project areas, interaction patterns, and friction points
/install-github-app Set up the Claude GitHub Actions app for a repository. Walks you through selecting a repo and configuring the integration
/install-slack-app Install the Claude Slack app. Opens a browser to complete the OAuth flow
/keybindings Open or create your keybindings configuration file
/login Sign in to your Anthropic account
/logout Sign out from your Anthropic account
/loop [interval] [prompt] Skill. Run a prompt repeatedly while the session stays open. Omit the interval and Claude self-paces between iterations. Omit the prompt and Claude runs an autonomous maintenance check, or the prompt in .claude/loop.md if present. Example: /loop 5m check if the deploy finished. See Run prompts on a schedule. Alias: /proactive
/mcp Manage MCP server connections and OAuth authentication
/memory Edit CLAUDE.md memory files, enable or disable auto-memory, and view auto-memory entries
/mobile Show QR code to download the Claude mobile app. Aliases: /ios, /android
/model [model] Select or change the AI model. For models that support it, use left/right arrows to adjust effort level. The change takes effect immediately without waiting for the current response to finish
/passes Share a free week of Claude Code with friends. Only visible if your account is eligible
/permissions Manage allow, ask, and deny rules for tool permissions. Opens an interactive dialog where you can view rules by scope, add or remove rules, manage working directories, and review recent auto mode denials. Alias: /allowed-tools
/plan [description] Enter plan mode directly from the prompt. Pass an optional description to enter plan mode and immediately start with that task, for example /plan fix the auth bug
/plugin Manage Claude Code plugins
/powerup Discover Claude Code features through quick interactive lessons with animated demos
/pr-comments [PR] {/* max-version: 2.1.90 */}Removed in v2.1.91. Ask Claude directly to view pull request comments instead. On earlier versions, fetches and displays comments from a GitHub pull request; automatically detects the PR for the current branch, or pass a PR URL or number. Requires the gh CLI
/privacy-settings View and update your privacy settings. Only available for Pro and Max plan subscribers
/release-notes View the changelog in an interactive version picker. Select a specific version to see its release notes, or choose to show all versions
/reload-plugins Reload all active plugins to apply pending changes without restarting. Reports counts for each reloaded component and flags any load errors
/remote-control Make this session available for remote control from claude.ai. Alias: /rc
/remote-env Configure the default remote environment for web sessions started with --remote
/rename [name] Rename the current session and show the name on the prompt bar. Without a name, auto-generates one from conversation history
/resume [session] Resume a conversation by ID or name, or open the session picker. Alias: /continue
/review Deprecated. Install the code-review plugin instead: claude plugin install code-review@claude-plugins-official
/rewind Rewind the conversation and/or code to a previous point, or summarize from a selected message. See checkpointing. Alias: /checkpoint
/sandbox Toggle sandbox mode. Available on supported platforms only
/schedule [description] Create, update, list, or run Cloud scheduled tasks. Claude walks you through the setup conversationally
/security-review Analyze pending changes on the current branch for security vulnerabilities. Reviews the git diff and identifies risks like injection, auth issues, and data exposure
/setup-bedrock Configure Amazon Bedrock authentication, region, and model pins through an interactive wizard. Only visible when CLAUDE_CODE_USE_BEDROCK=1 is set. First-time Bedrock users can also access this wizard from the login screen
/setup-vertex Configure Google Vertex AI authentication, project, region, and model pins through an interactive wizard. Only visible when CLAUDE_CODE_USE_VERTEX=1 is set. First-time Vertex AI users can also access this wizard from the login screen
/simplify [focus] Skill. Review your recently changed files for code reuse, quality, and efficiency issues, then fix them. Spawns three review agents in parallel, aggregates their findings, and applies fixes. Pass text to focus on specific concerns: /simplify focus on memory efficiency
/skills List available skills
/stats Visualize daily usage, session history, streaks, and model preferences
/status Open the Settings interface (Status tab) showing version, model, account, and connectivity. Works while Claude is responding, without waiting for the current response to finish
/statusline Configure Claude Code's status line. Describe what you want, or run without arguments to auto-configure from your shell prompt
/stickers Order Claude Code stickers
/tasks List and manage background tasks. Also available as /bashes
/team-onboarding Generate a team onboarding guide from your Claude Code usage history. Claude analyzes your sessions, commands, and MCP server usage from the past 30 days and produces a markdown guide a teammate can paste as a first message to get set up quickly
/teleport Pull a Claude Code on the web session into this terminal: opens a picker, then fetches the branch and conversation. Also available as /tp. Requires a claude.ai subscription
/terminal-setup Configure terminal keybindings for Shift+Enter and other shortcuts. Only visible in terminals that need it, like VS Code, Alacritty, or Warp
/theme Change the color theme. Includes light and dark variants, colorblind-accessible (daltonized) themes, and ANSI themes that use your terminal's color palette
/ultraplan <prompt> Draft a plan in an ultraplan session, review it in your browser, then execute remotely or send it back to your terminal
/upgrade Open the upgrade page to switch to a higher plan tier
/usage Show plan usage limits and rate limit status
/vim {/* max-version: 2.1.91 */}Removed in v2.1.92. To toggle between Vim and Normal editing modes, use /config → Editor mode
/voice Toggle push-to-talk voice dictation. Requires a Claude.ai account
/web-setup Connect your GitHub account to Claude Code on the web using your local gh CLI credentials. /schedule prompts for this automatically if GitHub isn't connected

MCP prompts

MCP servers can expose prompts that appear as commands. These use the format /mcp__<server>__<prompt> and are dynamically discovered from connected servers. See MCP prompts for details.

See also

Claude Code supports the following environment variables to control its behavior. Set them in your shell before launching claude, or configure them in settings.json under the env key to apply them to every session or roll them out across your team.

Variable Purpose
ANTHROPIC_API_KEY API key sent as X-Api-Key header. When set, this key is used instead of your Claude Pro, Max, Team, or Enterprise subscription even if you are logged in. In non-interactive mode (-p), the key is always used when present. In interactive mode, you are prompted to approve the key once before it overrides your subscription. To use your subscription instead, run unset ANTHROPIC_API_KEY
ANTHROPIC_AUTH_TOKEN Custom value for the Authorization header (the value you set here will be prefixed with Bearer )
ANTHROPIC_BASE_URL Override the API endpoint to route requests through a proxy or gateway. When set to a non-first-party host, MCP tool search is disabled by default. Set ENABLE_TOOL_SEARCH=true if your proxy forwards tool_reference blocks
ANTHROPIC_BEDROCK_BASE_URL Override the Bedrock endpoint URL. Use for custom Bedrock endpoints or when routing through an LLM gateway. See Amazon Bedrock
ANTHROPIC_BEDROCK_MANTLE_BASE_URL Override the Bedrock Mantle endpoint URL. See Mantle endpoint
ANTHROPIC_BETAS Comma-separated list of additional anthropic-beta header values to include in API requests. Claude Code already sends the beta headers it needs; use this to opt into an Anthropic API beta before Claude Code adds native support. Unlike the --betas flag, which requires API key authentication, this variable works with all auth methods including Claude.ai subscription
ANTHROPIC_CUSTOM_HEADERS Custom headers to add to requests (Name: Value format, newline-separated for multiple headers)
ANTHROPIC_CUSTOM_MODEL_OPTION Model ID to add as a custom entry in the /model picker. Use this to make a non-standard or gateway-specific model selectable without replacing built-in aliases. See Model configuration
ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION Display description for the custom model entry in the /model picker. Defaults to Custom model (<model-id>) when not set
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME Display name for the custom model entry in the /model picker. Defaults to the model ID when not set
ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES See Model configuration
ANTHROPIC_DEFAULT_HAIKU_MODEL See Model configuration
ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION See Model configuration
ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME See Model configuration
ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES See Model configuration
ANTHROPIC_DEFAULT_OPUS_MODEL See Model configuration
ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION See Model configuration
ANTHROPIC_DEFAULT_OPUS_MODEL_NAME See Model configuration
ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES See Model configuration
ANTHROPIC_DEFAULT_SONNET_MODEL See Model configuration
ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION See Model configuration
ANTHROPIC_DEFAULT_SONNET_MODEL_NAME See Model configuration
ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES See Model configuration
ANTHROPIC_FOUNDRY_API_KEY API key for Microsoft Foundry authentication (see Microsoft Foundry)
ANTHROPIC_FOUNDRY_BASE_URL Full base URL for the Foundry resource (for example, https://my-resource.services.ai.azure.com/anthropic). Alternative to ANTHROPIC_FOUNDRY_RESOURCE (see Microsoft Foundry)
ANTHROPIC_FOUNDRY_RESOURCE Foundry resource name (for example, my-resource). Required if ANTHROPIC_FOUNDRY_BASE_URL is not set (see Microsoft Foundry)
ANTHROPIC_MODEL Name of the model setting to use (see Model Configuration)
ANTHROPIC_SMALL_FAST_MODEL [DEPRECATED] Name of Haiku-class model for background tasks
ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION Override AWS region for the Haiku-class model when using Bedrock or Bedrock Mantle
ANTHROPIC_VERTEX_BASE_URL Override the Vertex AI endpoint URL. Use for custom Vertex endpoints or when routing through an LLM gateway. See Google Vertex AI
ANTHROPIC_VERTEX_PROJECT_ID GCP project ID for Vertex AI. Required when using Google Vertex AI
API_TIMEOUT_MS Timeout for API requests in milliseconds (default: 600000, or 10 minutes; maximum: 2147483647). Increase this when requests time out on slow networks or when routing through a proxy. Values above the maximum overflow the underlying timer and cause requests to fail immediately
AWS_BEARER_TOKEN_BEDROCK Bedrock API key for authentication (see Bedrock API keys)
BASH_DEFAULT_TIMEOUT_MS Default timeout for long-running bash commands (default: 120000, or 2 minutes)
BASH_MAX_OUTPUT_LENGTH Maximum number of characters in bash outputs before they are middle-truncated
BASH_MAX_TIMEOUT_MS Maximum timeout the model can set for long-running bash commands (default: 600000, or 10 minutes)
CCR_FORCE_BUNDLE Set to 1 to force claude --remote to bundle and upload your local repository even when GitHub access is available
CLAUDECODE Set to 1 in shell environments Claude Code spawns (Bash tool, tmux sessions). Not set in hooks or status line commands. Use to detect when a script is running inside a shell spawned by Claude Code
CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS Set to 1 to disable all built-in subagent types such as Explore and Plan. Only applies in non-interactive mode (the -p flag). Useful for SDK users who want a blank slate
CLAUDE_AGENT_SDK_MCP_NO_PREFIX Set to 1 to skip the mcp__<server>__ prefix on tool names from SDK-created MCP servers. Tools use their original names. SDK usage only
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE Set the percentage of context capacity (1-100) at which auto-compaction triggers. By default, auto-compaction triggers at approximately 95% capacity. Use lower values like 50 to compact earlier. Values above the default threshold have no effect. Applies to both main conversations and subagents. This percentage aligns with the context_window.used_percentage field available in status line
CLAUDE_AUTO_BACKGROUND_TASKS Set to 1 to force-enable automatic backgrounding of long-running agent tasks. When enabled, subagents are moved to the background after running for approximately two minutes
CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR Return to the original working directory after each Bash or PowerShell command in the main session
CLAUDE_CODE_ACCESSIBILITY Set to 1 to keep the native terminal cursor visible and disable the inverted-text cursor indicator. Allows screen magnifiers like macOS Zoom to track cursor position
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD Set to 1 to load memory files from directories specified with --add-dir. Loads CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md, and CLAUDE.local.md. By default, additional directories do not load memory files
CLAUDE_CODE_API_KEY_HELPER_TTL_MS Interval in milliseconds at which credentials should be refreshed (when using apiKeyHelper)
CLAUDE_CODE_AUTO_COMPACT_WINDOW Set the context capacity in tokens used for auto-compaction calculations. Defaults to the model's context window: 200K for standard models or 1M for extended context models. Use a lower value like 500000 on a 1M model to treat the window as 500K for compaction purposes. The value is capped at the model's actual context window. CLAUDE_AUTOCOMPACT_PCT_OVERRIDE is applied as a percentage of this value. Setting this variable decouples the compaction threshold from the status line's used_percentage, which always uses the model's full context window
CLAUDE_CODE_AUTO_CONNECT_IDE Override automatic IDE connection. By default, Claude Code connects automatically when launched inside a supported IDE's integrated terminal. Set to false to prevent this. Set to true to force a connection attempt when auto-detection fails, such as when tmux obscures the parent terminal
CLAUDE_CODE_CERT_STORE Comma-separated list of CA certificate sources for TLS connections. bundled is the Mozilla CA set shipped with Claude Code. system is the operating system trust store. Default is bundled,system. The native binary distribution is required for system store integration. On the Node.js runtime, only the bundled set is used regardless of this value
CLAUDE_CODE_CLIENT_CERT Path to client certificate file for mTLS authentication
CLAUDE_CODE_CLIENT_KEY Path to client private key file for mTLS authentication
CLAUDE_CODE_CLIENT_KEY_PASSPHRASE Passphrase for encrypted CLAUDE_CODE_CLIENT_KEY (optional)
CLAUDE_CODE_DEBUG_LOGS_DIR Override the debug log file path. Despite the name, this is a file path, not a directory. Requires debug mode to be enabled separately via --debug or /debug: setting this variable alone does not enable logging. The --debug-file flag does both at once. Defaults to ~/.claude/debug/<session-id>.txt
CLAUDE_CODE_DEBUG_LOG_LEVEL Minimum log level written to the debug log file. Values: verbose, debug (default), info, warn, error. Set to verbose to include high-volume diagnostics like full status line command output, or raise to error to reduce noise
CLAUDE_CODE_DISABLE_1M_CONTEXT Set to 1 to disable 1M context window support. When set, 1M model variants are unavailable in the model picker. Useful for enterprise environments with compliance requirements
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING Set to 1 to disable adaptive reasoning for Opus 4.6 and Sonnet 4.6. When disabled, these models fall back to the fixed thinking budget controlled by MAX_THINKING_TOKENS
CLAUDE_CODE_DISABLE_ATTACHMENTS Set to 1 to disable attachment processing. File mentions with @ syntax are sent as plain text instead of being expanded into file content
CLAUDE_CODE_DISABLE_AUTO_MEMORY Set to 1 to disable auto memory. Set to 0 to force auto memory on during the gradual rollout. When disabled, Claude does not create or load auto memory files
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS Set to 1 to disable all background task functionality, including the run_in_background parameter on Bash and subagent tools, auto-backgrounding, and the Ctrl+B shortcut
CLAUDE_CODE_DISABLE_CLAUDE_MDS Set to 1 to prevent loading any CLAUDE.md memory files into context, including user, project, and auto-memory files
CLAUDE_CODE_DISABLE_CRON Set to 1 to disable scheduled tasks. The /loop skill and cron tools become unavailable and any already-scheduled tasks stop firing, including tasks that are already running mid-session
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS Set to 1 to strip Anthropic-specific anthropic-beta request headers and beta tool-schema fields (such as defer_loading and eager_input_streaming) from API requests. Use this when a proxy gateway rejects requests with errors like "Unexpected value(s) for the anthropic-beta header" or "Extra inputs are not permitted". Standard fields (name, description, input_schema, cache_control) are preserved.
CLAUDE_CODE_DISABLE_FAST_MODE Set to 1 to disable fast mode
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY Set to 1 to disable the "How is Claude doing?" session quality surveys. Surveys are also disabled when DISABLE_TELEMETRY or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set. See Session quality surveys
CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING Set to 1 to disable file checkpointing. The /rewind command will not be able to restore code changes
CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS Set to 1 to remove built-in commit and PR workflow instructions and the git status snapshot from Claude's system prompt. Useful when using your own git workflow skills. Takes precedence over the includeGitInstructions setting when set
CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP Set to 1 to prevent automatic remapping of Opus 4.0 and 4.1 to the current Opus version on the Anthropic API. Use when you intentionally want to pin an older model. The remap does not run on Bedrock, Vertex, or Foundry
CLAUDE_CODE_DISABLE_MOUSE Set to 1 to disable mouse tracking in fullscreen rendering. Keyboard scrolling with PgUp and PgDn still works. Use this to keep your terminal's native copy-on-select behavior
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC Equivalent of setting DISABLE_AUTOUPDATER, DISABLE_FEEDBACK_COMMAND, DISABLE_ERROR_REPORTING, and DISABLE_TELEMETRY
CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK Set to 1 to disable the non-streaming fallback when a streaming request fails mid-stream. Streaming errors propagate to the retry layer instead. Useful when a proxy or gateway causes the fallback to produce duplicate tool execution
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL Set to 1 to skip automatic addition of the official plugin marketplace on first run
CLAUDE_CODE_DISABLE_TERMINAL_TITLE Set to 1 to disable automatic terminal title updates based on conversation context
CLAUDE_CODE_DISABLE_THINKING Set to 1 to force-disable extended thinking regardless of model support or other settings. More direct than MAX_THINKING_TOKENS=0
CLAUDE_CODE_EFFORT_LEVEL Set the effort level for supported models. Values: low, medium, high, max (Opus 4.6 only), or auto to use the model default. Takes precedence over /effort and the effortLevel setting. See Adjust effort level
CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING Set to 1 to force-enable fine-grained tool input streaming. Without this, the API buffers tool input parameters fully before sending delta events, which can delay display on large tool inputs. Anthropic API only: has no effect on Bedrock, Vertex, or Foundry
CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION Set to false to disable prompt suggestions (the "Prompt suggestions" toggle in /config). These are the grayed-out predictions that appear in your prompt input after Claude responds. See Prompt suggestions
CLAUDE_CODE_ENABLE_TASKS Set to 1 to enable the task tracking system in non-interactive mode (the -p flag). Tasks are on by default in interactive mode. See Task list
CLAUDE_CODE_ENABLE_TELEMETRY Set to 1 to enable OpenTelemetry data collection for metrics and logging. Required before configuring OTel exporters. See Monitoring
CLAUDE_CODE_EXIT_AFTER_STOP_DELAY Time in milliseconds to wait after the query loop becomes idle before automatically exiting. Useful for automated workflows and scripts using SDK mode
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS Set to 1 to enable agent teams. Agent teams are experimental and disabled by default
CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS Override the default token limit for file reads. Useful when you need to read larger files in full
CLAUDE_CODE_GIT_BASH_PATH Windows only: path to the Git Bash executable (bash.exe). Use when Git Bash is installed but not in your PATH. See Windows setup
CLAUDE_CODE_GLOB_HIDDEN Set to false to exclude dotfiles from results when Claude invokes the Glob tool. Included by default. Does not affect @ file autocomplete, ls, Grep, or Read
CLAUDE_CODE_GLOB_NO_IGNORE Set to false to make the Glob tool respect .gitignore patterns. By default, Glob returns all matching files including gitignored ones. Does not affect @ file autocomplete, which has its own respectGitignore setting
CLAUDE_CODE_GLOB_TIMEOUT_SECONDS Timeout in seconds for Glob tool file discovery. Defaults to 20 seconds on most platforms and 60 seconds on WSL
CLAUDE_CODE_IDE_HOST_OVERRIDE Override the host address used to connect to the IDE extension. By default Claude Code auto-detects the correct address, including WSL-to-Windows routing
CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL Skip auto-installation of IDE extensions. Equivalent to setting autoInstallIdeExtension to false
CLAUDE_CODE_IDE_SKIP_VALID_CHECK Set to 1 to skip validation of IDE lockfile entries during connection. Use when auto-connect fails to find your IDE despite it running
CLAUDE_CODE_MAX_OUTPUT_TOKENS Set the maximum number of output tokens for most requests. Defaults and caps vary by model; see max output tokens. Increasing this value reduces the effective context window available before auto-compaction triggers.
CLAUDE_CODE_MAX_RETRIES Override the number of times to retry failed API requests (default: 10)
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY Maximum number of read-only tools and subagents that can execute in parallel (default: 10). Higher values increase parallelism but consume more resources
CLAUDE_CODE_NEW_INIT Set to 1 to make /init run an interactive setup flow. The flow asks which files to generate, including CLAUDE.md, skills, and hooks, before exploring the codebase and writing them. Without this variable, /init generates a CLAUDE.md automatically without prompting.
CLAUDE_CODE_NO_FLICKER Set to 1 to enable fullscreen rendering, a research preview that reduces flicker and keeps memory flat in long conversations
CLAUDE_CODE_OAUTH_REFRESH_TOKEN OAuth refresh token for Claude.ai authentication. When set, claude auth login exchanges this token directly instead of opening a browser. Requires CLAUDE_CODE_OAUTH_SCOPES. Useful for provisioning authentication in automated environments
CLAUDE_CODE_OAUTH_SCOPES Space-separated OAuth scopes the refresh token was issued with, such as "user:profile user:inference user:sessions:claude_code". Required when CLAUDE_CODE_OAUTH_REFRESH_TOKEN is set
CLAUDE_CODE_OAUTH_TOKEN OAuth access token for Claude.ai authentication. Alternative to /login for SDK and automated environments. Takes precedence over keychain-stored credentials. Generate one with claude setup-token
CLAUDE_CODE_OTEL_FLUSH_TIMEOUT_MS Timeout in milliseconds for flushing pending OpenTelemetry spans (default: 5000). See Monitoring
CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS Interval for refreshing dynamic OpenTelemetry headers in milliseconds (default: 1740000 / 29 minutes). See Dynamic headers
CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS Timeout in milliseconds for the OpenTelemetry exporter to finish on shutdown (default: 2000). Increase if metrics are dropped at exit. See Monitoring
CLAUDE_CODE_PERFORCE_MODE Set to 1 to enable Perforce-aware write protection. When set, Edit, Write, and NotebookEdit fail with a p4 edit <file> hint if the target file lacks the owner-write bit, which Perforce clears on synced files until p4 edit opens them. This prevents Claude Code from bypassing Perforce change tracking
CLAUDE_CODE_PLUGIN_CACHE_DIR Override the plugins root directory. Despite the name, this sets the parent directory, not the cache itself: marketplaces and the plugin cache live in subdirectories under this path. Defaults to ~/.claude/plugins
CLAUDE_CODE_PLUGIN_GIT_TIMEOUT_MS Timeout in milliseconds for git operations when installing or updating plugins (default: 120000). Increase this value for large repositories or slow network connections. See Git operations time out
CLAUDE_CODE_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE Set to 1 to keep the existing marketplace cache when a git pull fails instead of wiping and re-cloning. Useful in offline or airgapped environments where re-cloning would fail the same way. See Marketplace updates fail in offline environments
CLAUDE_CODE_PLUGIN_SEED_DIR Path to one or more read-only plugin seed directories, separated by : on Unix or ; on Windows. Use this to bundle a pre-populated plugins directory into a container image. Claude Code registers marketplaces from these directories at startup and uses pre-cached plugins without re-cloning. See Pre-populate plugins for containers
CLAUDE_CODE_PROXY_RESOLVES_HOSTS Set to 1 to allow the proxy to perform DNS resolution instead of the caller. Opt-in for environments where the proxy should handle hostname resolution
CLAUDE_CODE_RESUME_INTERRUPTED_TURN Set to 1 to automatically resume if the previous session ended mid-turn. Used in SDK mode so the model continues without requiring the SDK to re-send the prompt
CLAUDE_CODE_SCRIPT_CAPS JSON object limiting how many times specific scripts may be invoked per session when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set. Keys are substrings matched against the command text; values are integer call limits. For example, {"deploy.sh": 2} allows deploy.sh to be called at most twice. Matching is substring-based so shell-expansion tricks like ./scripts/deploy.sh $(evil) still count against the cap. Runtime fan-out via xargs or find -exec is not detected; this is a defense-in-depth control
CLAUDE_CODE_SCROLL_SPEED Set the mouse wheel scroll multiplier in fullscreen rendering. Accepts values from 1 to 20. Set to 3 to match vim if your terminal sends one wheel event per notch without amplification
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS Override the time budget in milliseconds for SessionEnd hooks. Applies to session exit, /clear, and switching sessions via interactive /resume. By default the budget is 1.5 seconds, automatically raised to the highest per-hook timeout configured in settings files, up to 60 seconds. Timeouts on plugin-provided hooks do not raise the budget
CLAUDE_CODE_SHELL Override automatic shell detection. Useful when your login shell differs from your preferred working shell (for example, bash vs zsh)
CLAUDE_CODE_SHELL_PREFIX Command prefix to wrap all bash commands (for example, for logging or auditing). Example: /path/to/logger.sh will execute /path/to/logger.sh <command>
CLAUDE_CODE_SIMPLE Set to 1 to run with a minimal system prompt and only the Bash, file read, and file edit tools. MCP tools from --mcp-config are still available. Disables auto-discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md. The --bare CLI flag sets this
CLAUDE_CODE_SKIP_BEDROCK_AUTH Skip AWS authentication for Bedrock (for example, when using an LLM gateway)
CLAUDE_CODE_SKIP_FOUNDRY_AUTH Skip Azure authentication for Microsoft Foundry (for example, when using an LLM gateway)
CLAUDE_CODE_SKIP_MANTLE_AUTH Skip AWS authentication for Bedrock Mantle (for example, when using an LLM gateway)
CLAUDE_CODE_SKIP_VERTEX_AUTH Skip Google authentication for Vertex (for example, when using an LLM gateway)
CLAUDE_CODE_SUBAGENT_MODEL See Model configuration
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB Set to 1 to strip Anthropic and cloud provider credentials from subprocess environments (Bash tool, hooks, MCP stdio servers). The parent Claude process keeps these credentials for API calls, but child processes cannot read them, reducing exposure to prompt injection attacks that attempt to exfiltrate secrets via shell expansion. On Linux, this also runs Bash subprocesses in an isolated PID namespace so they cannot read host process environments via /proc; as a side effect, ps, pgrep, and kill cannot see or signal host processes. claude-code-action sets this automatically when allowed_non_write_users is configured
CLAUDE_CODE_SYNC_PLUGIN_INSTALL Set to 1 in non-interactive mode (the -p flag) to wait for plugin installation to complete before the first query. Without this, plugins install in the background and may not be available on the first turn. Combine with CLAUDE_CODE_SYNC_PLUGIN_INSTALL_TIMEOUT_MS to bound the wait
CLAUDE_CODE_SYNC_PLUGIN_INSTALL_TIMEOUT_MS Timeout in milliseconds for synchronous plugin installation. When exceeded, Claude Code proceeds without plugins and logs an error. No default: without this variable, synchronous installation waits until complete
CLAUDE_CODE_SYNTAX_HIGHLIGHT Set to false to disable syntax highlighting in diff output. Useful when colors interfere with your terminal setup
CLAUDE_CODE_TASK_LIST_ID Share a task list across sessions. Set the same ID in multiple Claude Code instances to coordinate on a shared task list. See Task list
CLAUDE_CODE_TEAM_NAME Name of the agent team this teammate belongs to. Set automatically on agent team members
CLAUDE_CODE_TMPDIR Override the temp directory used for internal temp files. Claude Code appends /claude-{uid}/ (Unix) or /claude/ (Windows) to this path. Default: /tmp on macOS, os.tmpdir on Linux/Windows
CLAUDE_CODE_USE_BEDROCK Use Bedrock
CLAUDE_CODE_USE_FOUNDRY Use Microsoft Foundry
CLAUDE_CODE_USE_MANTLE Use the Bedrock Mantle endpoint
CLAUDE_CODE_USE_POWERSHELL_TOOL Set to 1 to enable the PowerShell tool on Windows (opt-in preview). When enabled, Claude can run PowerShell commands natively instead of routing through Git Bash. Only supported on native Windows, not WSL. See PowerShell tool
CLAUDE_CODE_USE_VERTEX Use Vertex
CLAUDE_CONFIG_DIR Override the configuration directory (default: ~/.claude). All settings, credentials, session history, and plugins are stored under this path. Useful for running multiple accounts side by side: for example, alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'
CLAUDE_ENABLE_BYTE_WATCHDOG Set to 1 to force-enable the byte-level streaming idle watchdog, or set to 0 to force-disable it. When unset, the watchdog is enabled by default for Anthropic API connections. The byte watchdog aborts a connection when no bytes arrive on the wire for the duration set by CLAUDE_STREAM_IDLE_TIMEOUT_MS, with a minimum of 5 minutes, independent of the event-level watchdog
CLAUDE_ENABLE_STREAM_WATCHDOG Set to 1 to enable the event-level streaming idle watchdog. Off by default. For Bedrock, Vertex, and Foundry, this is the only idle watchdog available. Configure the timeout with CLAUDE_STREAM_IDLE_TIMEOUT_MS
CLAUDE_ENV_FILE Path to a shell script that Claude Code sources before each Bash command. Use to persist virtualenv or conda activation across commands. Also populated dynamically by SessionStart, CwdChanged, and FileChanged hooks
CLAUDE_REMOTE_CONTROL_SESSION_NAME_PREFIX Prefix for auto-generated Remote Control session names when no explicit name is provided. Defaults to your machine's hostname, producing names like myhost-graceful-unicorn. The --remote-control-session-name-prefix CLI flag sets the same value for a single invocation
CLAUDE_STREAM_IDLE_TIMEOUT_MS Timeout in milliseconds before the streaming idle watchdog closes a stalled connection. For the byte-level watchdog on the Anthropic API: default and minimum 300000 (5 minutes); lower values are silently clamped to absorb extended thinking pauses and proxy buffering. For the event-level watchdog: default 90000 (90 seconds), no minimum. For third-party providers, requires CLAUDE_ENABLE_STREAM_WATCHDOG=1
DISABLE_AUTOUPDATER Set to 1 to disable automatic updates
DISABLE_AUTO_COMPACT Set to 1 to disable automatic compaction when approaching the context limit. The manual /compact command remains available. Use when you want explicit control over when compaction occurs
DISABLE_COMPACT Set to 1 to disable all compaction: both automatic compaction and the manual /compact command
DISABLE_COST_WARNINGS Set to 1 to disable cost warning messages
DISABLE_DOCTOR_COMMAND Set to 1 to hide the /doctor command. Useful for managed deployments where users should not run installation diagnostics
DISABLE_ERROR_REPORTING Set to 1 to opt out of Sentry error reporting
DISABLE_EXTRA_USAGE_COMMAND Set to 1 to hide the /extra-usage command that lets users purchase additional usage beyond rate limits
DISABLE_FEEDBACK_COMMAND Set to 1 to disable the /feedback command. The older name DISABLE_BUG_COMMAND is also accepted
DISABLE_INSTALLATION_CHECKS Set to 1 to disable installation warnings. Use only when manually managing the installation location, as this can mask issues with standard installations
DISABLE_INSTALL_GITHUB_APP_COMMAND Set to 1 to hide the /install-github-app command. Already hidden when using third-party providers (Bedrock, Vertex, or Foundry)
DISABLE_INTERLEAVED_THINKING Set to 1 to prevent sending the interleaved-thinking beta header. Useful when your LLM gateway or provider does not support interleaved thinking
DISABLE_LOGIN_COMMAND Set to 1 to hide the /login command. Useful when authentication is handled externally via API keys or apiKeyHelper
DISABLE_LOGOUT_COMMAND Set to 1 to hide the /logout command
DISABLE_PROMPT_CACHING Set to 1 to disable prompt caching for all models (takes precedence over per-model settings)
DISABLE_PROMPT_CACHING_HAIKU Set to 1 to disable prompt caching for Haiku models
DISABLE_PROMPT_CACHING_OPUS Set to 1 to disable prompt caching for Opus models
DISABLE_PROMPT_CACHING_SONNET Set to 1 to disable prompt caching for Sonnet models
DISABLE_TELEMETRY Set to 1 to opt out of Statsig telemetry (note that Statsig events do not include user data like code, file paths, or bash commands)
DISABLE_UPGRADE_COMMAND Set to 1 to hide the /upgrade command
ENABLE_CLAUDEAI_MCP_SERVERS Set to false to disable claude.ai MCP servers in Claude Code. Enabled by default for logged-in users
ENABLE_PROMPT_CACHING_1H_BEDROCK Set to 1 when using Bedrock to request a 1-hour prompt cache TTL instead of the default 5 minutes. Bedrock only
ENABLE_TOOL_SEARCH Controls MCP tool search. Unset: all MCP tools deferred by default, but loaded upfront when ANTHROPIC_BASE_URL points to a non-first-party host. Values: true (always defer including proxies), auto (threshold mode: load upfront if tools fit within 10% of context), auto:N (custom threshold, e.g., auto:5 for 5%), false (load all upfront)
FALLBACK_FOR_ALL_PRIMARY_MODELS Set to any non-empty value to trigger fallback to --fallback-model after repeated overload errors on any primary model. By default, only Opus models trigger the fallback
FORCE_AUTOUPDATE_PLUGINS Set to 1 to force plugin auto-updates even when the main auto-updater is disabled via DISABLE_AUTOUPDATER
HTTP_PROXY Specify HTTP proxy server for network connections
HTTPS_PROXY Specify HTTPS proxy server for network connections
IS_DEMO Set to 1 to enable demo mode: hides your email and organization name from the header and /status output, and skips onboarding. Useful when streaming or recording a session
MAX_MCP_OUTPUT_TOKENS Maximum number of tokens allowed in MCP tool responses. Claude Code displays a warning when output exceeds 10,000 tokens. Tools that declare anthropic/maxResultSizeChars use that character limit for text content instead, but image content from those tools is still subject to this variable (default: 25000)
MAX_STRUCTURED_OUTPUT_RETRIES Number of times to retry when the model's response fails validation against the --json-schema in non-interactive mode (the -p flag). Defaults to 5
MAX_THINKING_TOKENS Override the extended thinking token budget. The ceiling is the model's max output tokens minus one. Set to 0 to disable thinking entirely. On models with adaptive reasoning (Opus 4.6, Sonnet 4.6), the budget is ignored unless adaptive reasoning is disabled via CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING
MCP_CLIENT_SECRET OAuth client secret for MCP servers that require pre-configured credentials. Avoids the interactive prompt when adding a server with --client-secret
MCP_CONNECTION_NONBLOCKING Set to true in non-interactive mode (-p) to skip the MCP connection wait entirely. Useful for scripted pipelines where MCP tools are not needed. Without this variable, the first query waits up to 5 seconds for --mcp-config server connections
MCP_OAUTH_CALLBACK_PORT Fixed port for the OAuth redirect callback, as an alternative to --callback-port when adding an MCP server with pre-configured credentials
MCP_REMOTE_SERVER_CONNECTION_BATCH_SIZE Maximum number of remote MCP servers (HTTP/SSE) to connect in parallel during startup (default: 20)
MCP_SERVER_CONNECTION_BATCH_SIZE Maximum number of local MCP servers (stdio) to connect in parallel during startup (default: 3)
MCP_TIMEOUT Timeout in milliseconds for MCP server startup (default: 30000, or 30 seconds)
MCP_TOOL_TIMEOUT Timeout in milliseconds for MCP tool execution (default: 100000000, about 28 hours)
NO_PROXY List of domains and IPs to which requests will be directly issued, bypassing proxy
OTEL_LOG_TOOL_CONTENT Set to 1 to include tool input and output content in OpenTelemetry span events. Disabled by default to protect sensitive data. See Monitoring
OTEL_LOG_TOOL_DETAILS Set to 1 to include tool input arguments, MCP server names, and tool details in OpenTelemetry traces and logs. Disabled by default to protect PII. See Monitoring
OTEL_LOG_USER_PROMPTS Set to 1 to include user prompt text in OpenTelemetry traces and logs. Disabled by default (prompts are redacted). See Monitoring
OTEL_METRICS_INCLUDE_ACCOUNT_UUID Set to false to exclude account UUID from metrics attributes (default: included). See Monitoring
OTEL_METRICS_INCLUDE_SESSION_ID Set to false to exclude session ID from metrics attributes (default: included). See Monitoring
OTEL_METRICS_INCLUDE_VERSION Set to true to include Claude Code version in metrics attributes (default: excluded). See Monitoring
SLASH_COMMAND_TOOL_CHAR_BUDGET Override the character budget for skill metadata shown to the Skill tool. The budget scales dynamically at 1% of the context window, with a fallback of 8,000 characters. Legacy name kept for backwards compatibility
TASK_MAX_OUTPUT_LENGTH Maximum number of characters in subagent output before truncation (default: 32000, maximum: 160000). When truncated, the full output is saved to disk and the path is included in the truncated response
USE_BUILTIN_RIPGREP Set to 0 to use system-installed rg instead of rg included with Claude Code
VERTEX_REGION_CLAUDE_3_5_HAIKU Override region for Claude 3.5 Haiku when using Vertex AI
VERTEX_REGION_CLAUDE_3_5_SONNET Override region for Claude 3.5 Sonnet when using Vertex AI
VERTEX_REGION_CLAUDE_3_7_SONNET Override region for Claude 3.7 Sonnet when using Vertex AI
VERTEX_REGION_CLAUDE_4_0_OPUS Override region for Claude 4.0 Opus when using Vertex AI
VERTEX_REGION_CLAUDE_4_0_SONNET Override region for Claude 4.0 Sonnet when using Vertex AI
VERTEX_REGION_CLAUDE_4_1_OPUS Override region for Claude 4.1 Opus when using Vertex AI
VERTEX_REGION_CLAUDE_4_5_OPUS Override region for Claude Opus 4.5 when using Vertex AI
VERTEX_REGION_CLAUDE_4_5_SONNET Override region for Claude Sonnet 4.5 when using Vertex AI
VERTEX_REGION_CLAUDE_4_6_OPUS Override region for Claude Opus 4.6 when using Vertex AI
VERTEX_REGION_CLAUDE_4_6_SONNET Override region for Claude Sonnet 4.6 when using Vertex AI
VERTEX_REGION_CLAUDE_HAIKU_4_5 Override region for Claude Haiku 4.5 when using Vertex AI

Standard OpenTelemetry exporter variables (OTEL_METRICS_EXPORTER, OTEL_LOGS_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS, OTEL_METRIC_EXPORT_INTERVAL, OTEL_RESOURCE_ATTRIBUTES, and signal-specific variants) are also supported. See Monitoring for configuration details.

See also

Claude Code has access to a set of built-in tools that help it understand and modify your codebase. The tool names are the exact strings you use in permission rules, subagent tool lists, and hook matchers. To disable a tool entirely, add its name to the deny array in your permission settings.

To add custom tools, connect an MCP server. To extend Claude with reusable prompt-based workflows, write a skill, which runs through the existing Skill tool rather than adding a new tool entry.

Tool Description Permission Required
Agent Spawns a subagent with its own context window to handle a task No
AskUserQuestion Asks multiple-choice questions to gather requirements or clarify ambiguity No
Bash Executes shell commands in your environment. See Bash tool behavior Yes
CronCreate Schedules a recurring or one-shot prompt within the current session (gone when Claude exits). See scheduled tasks No
CronDelete Cancels a scheduled task by ID No
CronList Lists all scheduled tasks in the session No
Edit Makes targeted edits to specific files Yes
EnterPlanMode Switches to plan mode to design an approach before coding No
EnterWorktree Creates an isolated git worktree and switches into it. Pass a path to switch into an existing worktree of the current repository instead of creating a new one No
ExitPlanMode Presents a plan for approval and exits plan mode Yes
ExitWorktree Exits a worktree session and returns to the original directory No
Glob Finds files based on pattern matching No
Grep Searches for patterns in file contents No
ListMcpResourcesTool Lists resources exposed by connected MCP servers No
LSP Code intelligence via language servers: jump to definitions, find references, report type errors and warnings. See LSP tool behavior No
Monitor Runs a command in the background and feeds each output line back to Claude, so it can react to log entries, file changes, or polled status mid-conversation. See Monitor tool Yes
NotebookEdit Modifies Jupyter notebook cells Yes
PowerShell Executes PowerShell commands on Windows. Opt-in preview. See PowerShell tool Yes
Read Reads the contents of files No
ReadMcpResourceTool Reads a specific MCP resource by URI No
SendMessage Sends a message to an agent team teammate, or resumes a subagent by its agent ID. Stopped subagents auto-resume in the background. Only available when CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set No
Skill Executes a skill within the main conversation Yes
TaskCreate Creates a new task in the task list No
TaskGet Retrieves full details for a specific task No
TaskList Lists all tasks with their current status No
TaskOutput (Deprecated) Retrieves output from a background task. Prefer Read on the task's output file path No
TaskStop Kills a running background task by ID No
TaskUpdate Updates task status, dependencies, details, or deletes tasks No
TeamCreate Creates an agent team with multiple teammates. Only available when CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set No
TeamDelete Disbands an agent team and cleans up teammate processes. Only available when CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 is set No
TodoWrite Manages the session task checklist. Available in non-interactive mode and the Agent SDK; interactive sessions use TaskCreate, TaskGet, TaskList, and TaskUpdate instead No
ToolSearch Searches for and loads deferred tools when tool search is enabled No
WebFetch Fetches content from a specified URL Yes
WebSearch Performs web searches Yes
Write Creates or overwrites files Yes

Permission rules can be configured using /permissions or in permission settings. Also see Tool-specific permission rules.

Bash tool behavior

The Bash tool runs each command in a separate process with the following persistence behavior:

  • When Claude runs cd in the main session, the new working directory carries over to later Bash commands as long as it stays inside the project directory or an additional working directory you added with --add-dir, /add-dir, or additionalDirectories in settings. Subagent sessions never carry over working directory changes.
    • If cd lands outside those directories, Claude Code resets to the project directory and appends Shell cwd was reset to <dir> to the tool result.
    • To disable this carry-over so every Bash command starts in the project directory, set CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1.
  • Environment variables do not persist. An export in one command will not be available in the next.

Activate your virtualenv or conda environment before launching Claude Code. To make environment variables persist across Bash commands, set CLAUDE_ENV_FILE to a shell script before launching Claude Code, or use a SessionStart hook to populate it dynamically.

LSP tool behavior

The LSP tool gives Claude code intelligence from a running language server. After each file edit, it automatically reports type errors and warnings so Claude can fix issues without a separate build step. Claude can also call it directly to navigate code:

  • Jump to a symbol's definition
  • Find all references to a symbol
  • Get type information at a position
  • List symbols in a file or workspace
  • Find implementations of an interface
  • Trace call hierarchies

The tool is inactive until you install a code intelligence plugin for your language. The plugin bundles the language server configuration, and you install the server binary separately.

Monitor tool

Note

The Monitor tool requires Claude Code v2.1.98 or later.

The Monitor tool lets Claude watch something in the background and react when it changes, without pausing the conversation. Ask Claude to:

  • Tail a log file and flag errors as they appear
  • Poll a PR or CI job and report when its status changes
  • Watch a directory for file changes
  • Track output from any long-running script you point it at

Claude writes a small script for the watch, runs it in the background, and receives each output line as it arrives. You keep working in the same session and Claude interjects when an event lands. Stop a monitor by asking Claude to cancel it or by ending the session.

Monitor uses the same permission rules as Bash, so allow and deny patterns you have set for Bash apply here too. It is not available on Amazon Bedrock, Google Vertex AI, or Microsoft Foundry. It is also not available when DISABLE_TELEMETRY or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set.

PowerShell tool

On Windows, Claude Code can run PowerShell commands natively instead of routing through Git Bash. This is an opt-in preview.

Enable the PowerShell tool

Set CLAUDE_CODE_USE_POWERSHELL_TOOL=1 in your environment or in settings.json:

{
  "env": {
    "CLAUDE_CODE_USE_POWERSHELL_TOOL": "1"
  }
}

Claude Code auto-detects pwsh.exe (PowerShell 7+) with a fallback to powershell.exe (PowerShell 5.1). The Bash tool remains registered alongside the PowerShell tool, so you may need to ask Claude to use PowerShell.

Shell selection in settings, hooks, and skills

Three additional settings control where PowerShell is used:

  • "defaultShell": "powershell" in settings.json: routes interactive ! commands through PowerShell. Requires the PowerShell tool to be enabled.
  • "shell": "powershell" on individual command hooks: runs that hook in PowerShell. Hooks spawn PowerShell directly, so this works regardless of CLAUDE_CODE_USE_POWERSHELL_TOOL.
  • shell: powershell in skill frontmatter: runs !`command` blocks in PowerShell. Requires the PowerShell tool to be enabled.

The same main-session working-directory reset behavior described under the Bash tool section applies to PowerShell commands, including the CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR environment variable.

Preview limitations

The PowerShell tool has the following known limitations during the preview:

  • Auto mode does not work with the PowerShell tool yet
  • PowerShell profiles are not loaded
  • Sandboxing is not supported
  • Only supported on native Windows, not WSL
  • Git Bash is still required to start Claude Code

Check which tools are available

Your exact tool set depends on your provider, platform, and settings. To check what's loaded in a running session, ask Claude directly:

What tools do you have access to?

Claude gives a conversational summary. For exact MCP tool names, run /mcp.

See also

  • MCP servers: add custom tools by connecting external servers
  • Permissions: permission system, rule syntax, and tool-specific patterns
  • Subagents: configure tool access for subagents
  • Hooks: run custom commands before or after tool execution

Keyboard shortcuts

Note

Keyboard shortcuts may vary by platform and terminal. Press ? to see available shortcuts for your environment.

macOS users: Option/Alt key shortcuts (Alt+B, Alt+F, Alt+Y, Alt+M, Alt+P, Alt+T) require configuring Option as Meta in your terminal:

  • iTerm2: settings → Profiles → Keys → set Left/Right Option key to "Esc+"
  • Terminal.app: settings → Profiles → Keyboard → check "Use Option as Meta Key"
  • VS Code: set "terminal.integrated.macOptionIsMeta": true in VS Code settings

See Terminal configuration for details.

General controls

Shortcut Description Context
Ctrl+C Cancel current input or generation Standard interrupt
Ctrl+X Ctrl+K Kill all background agents. Press twice within 3 seconds to confirm Background agent control
Ctrl+D Exit Claude Code session EOF signal
Ctrl+G or Ctrl+X Ctrl+E Open in default text editor Edit your prompt or custom response in your default text editor. Ctrl+X Ctrl+E is the readline-native binding
Ctrl+L Clear prompt input Clears typed text, keeps conversation history
Ctrl+O Toggle transcript viewer Shows detailed tool usage and execution. Also expands MCP calls, which collapse to a single line like "Called slack 3 times" by default. In fullscreen rendering, cycles through three states: normal prompt, transcript mode, and focus view (last prompt + tool summary + response)
Ctrl+R Reverse search command history Search through previous commands interactively
Ctrl+V or Cmd+V (iTerm2) or Alt+V (Windows) Paste image from clipboard Inserts an [Image #N] chip at the cursor so you can reference it positionally in your prompt
Ctrl+B Background running tasks Backgrounds bash commands and agents. Tmux users press twice
Ctrl+T Toggle task list Show or hide the task list in the terminal status area
Left/Right arrows Cycle through dialog tabs Navigate between tabs in permission dialogs and menus
Up/Down arrows Navigate command history Recall previous inputs
Esc + Esc Rewind or summarize Restore code and/or conversation to a previous point, or summarize from a selected message
Shift+Tab or Alt+M (some configurations) Cycle permission modes Cycle through default, acceptEdits, plan, and any modes you have enabled, such as auto or bypassPermissions. See permission modes.
Option+P (macOS) or Alt+P (Windows/Linux) Switch model Switch models without clearing your prompt
Option+T (macOS) or Alt+T (Windows/Linux) Toggle extended thinking Enable or disable extended thinking mode. On macOS, configure your terminal to send Option as Meta for this shortcut to work
Option+O (macOS) or Alt+O (Windows/Linux) Toggle fast mode Enable or disable fast mode

Text editing

Shortcut Description Context
Ctrl+K Delete to end of line Stores deleted text for pasting
Ctrl+U Delete from cursor to line start Stores deleted text for pasting. Repeat to clear across lines in multiline input
Ctrl+Y Paste deleted text Paste text deleted with Ctrl+K or Ctrl+U
Alt+Y (after Ctrl+Y) Cycle paste history After pasting, cycle through previously deleted text. Requires Option as Meta on macOS
Alt+B Move cursor back one word Word navigation. Requires Option as Meta on macOS
Alt+F Move cursor forward one word Word navigation. Requires Option as Meta on macOS

Theme and display

Shortcut Description Context
Ctrl+T Toggle syntax highlighting for code blocks Only works inside the /theme picker menu. Controls whether code in Claude's responses uses syntax coloring

Multiline input

Method Shortcut Context
Quick escape \ + Enter Works in all terminals
macOS default Option+Enter Default on macOS
Shift+Enter Shift+Enter Works out of the box in iTerm2, WezTerm, Ghostty, Kitty
Control sequence Ctrl+J Line feed character for multiline
Paste mode Paste directly For code blocks, logs

Tip

Shift+Enter works without configuration in iTerm2, WezTerm, Ghostty, and Kitty. For other terminals (VS Code, Alacritty, Zed, Warp), run /terminal-setup to install the binding.

Quick commands

Shortcut Description Notes
/ at start Command or skill See commands and skills
! at start Bash mode Run commands directly and add execution output to the session
@ File path mention Trigger file path autocomplete

Transcript viewer

When the transcript viewer is open (toggled with Ctrl+O), these shortcuts are available. Ctrl+E can be rebound via transcript:toggleShowAll.

Shortcut Description
Ctrl+E Toggle show all content
q, Ctrl+C, Esc Exit transcript view. All three can be rebound via transcript:exit

Voice input

Shortcut Description Notes
Hold Space Push-to-talk dictation Requires voice dictation to be enabled. Transcript inserts at cursor. Rebindable

Commands

Type / in Claude Code to see all available commands, or type / followed by any letters to filter. The / menu shows everything you can invoke: built-in commands, bundled and user-authored skills, and commands contributed by plugins and MCP servers. Not all built-in commands are visible to every user since some depend on your platform or plan.

See the commands reference for the full list of commands included in Claude Code.

Vim editor mode

Enable vim-style editing via /config → Editor mode.

Mode switching

Command Action From mode
Esc Enter NORMAL mode INSERT
i Insert before cursor NORMAL
I Insert at beginning of line NORMAL
a Insert after cursor NORMAL
A Insert at end of line NORMAL
o Open line below NORMAL
O Open line above NORMAL

Navigation (NORMAL mode)

Command Action
h/j/k/l Move left/down/up/right
w Next word
e End of word
b Previous word
0 Beginning of line
$ End of line
^ First non-blank character
gg Beginning of input
G End of input
f{char} Jump to next occurrence of character
F{char} Jump to previous occurrence of character
t{char} Jump to just before next occurrence of character
T{char} Jump to just after previous occurrence of character
; Repeat last f/F/t/T motion
, Repeat last f/F/t/T motion in reverse

Note

In vim normal mode, if the cursor is at the beginning or end of input and cannot move further, j/k and the arrow keys navigate command history instead.

Editing (NORMAL mode)

Command Action
x Delete character
dd Delete line
D Delete to end of line
dw/de/db Delete word/to end/back
cc Change line
C Change to end of line
cw/ce/cb Change word/to end/back
yy/Y Yank (copy) line
yw/ye/yb Yank word/to end/back
p Paste after cursor
P Paste before cursor
>> Indent line
<< Dedent line
J Join lines
. Repeat last change

Text objects (NORMAL mode)

Text objects work with operators like d, c, and y:

Command Action
iw/aw Inner/around word
iW/aW Inner/around WORD (whitespace-delimited)
i"/a" Inner/around double quotes
i'/a' Inner/around single quotes
i(/a( Inner/around parentheses
i[/a[ Inner/around brackets
i{/a{ Inner/around braces

Command history

Claude Code maintains command history for the current session:

  • Input history is stored per working directory
  • Input history resets when you run /clear to start a new session. The previous session's conversation is preserved and can be resumed.
  • Use Up/Down arrows to navigate (see keyboard shortcuts above)
  • Note: history expansion (!) is disabled by default

Reverse search with Ctrl+R

Press Ctrl+R to interactively search through your command history:

  1. Start search: press Ctrl+R to activate reverse history search
  2. Type query: enter text to search for in previous commands. The search term is highlighted in matching results
  3. Navigate matches: press Ctrl+R again to cycle through older matches
  4. Accept match:
    • Press Tab or Esc to accept the current match and continue editing
    • Press Enter to accept and execute the command immediately
  5. Cancel search:
    • Press Ctrl+C to cancel and restore your original input
    • Press Backspace on empty search to cancel

The search displays matching commands with the search term highlighted, so you can find and reuse previous inputs.

Background bash commands

Claude Code supports running bash commands in the background, allowing you to continue working while long-running processes execute.

How backgrounding works

When Claude Code runs a command in the background, it runs the command asynchronously and immediately returns a background task ID. Claude Code can respond to new prompts while the command continues executing in the background.

To run commands in the background, you can either:

  • Prompt Claude Code to run a command in the background
  • Press Ctrl+B to move a regular Bash tool invocation to the background. (Tmux users must press Ctrl+B twice due to tmux's prefix key.)

Key features:

  • Output is written to a file and Claude can retrieve it using the Read tool
  • Background tasks have unique IDs for tracking and output retrieval
  • Background tasks are automatically cleaned up when Claude Code exits
  • Background tasks are automatically terminated if output exceeds 5GB, with a note in stderr explaining why

To disable all background task functionality, set the CLAUDE_CODE_DISABLE_BACKGROUND_TASKS environment variable to 1. See Environment variables for details.

Common backgrounded commands:

  • Build tools (webpack, vite, make)
  • Package managers (npm, yarn, pnpm)
  • Test runners (jest, pytest)
  • Development servers
  • Long-running processes (docker, terraform)

Bash mode with ! prefix

Run bash commands directly without going through Claude by prefixing your input with !:

! npm test
! git status
! ls -la

Bash mode:

  • Adds the command and its output to the conversation context
  • Shows real-time progress and output
  • Supports the same Ctrl+B backgrounding for long-running commands
  • Does not require Claude to interpret or approve the command
  • Supports history-based autocomplete: type a partial command and press Tab to complete from previous ! commands in the current project
  • Exit with Escape, Backspace, or Ctrl+U on an empty prompt
  • Pasting text that starts with ! into an empty prompt enters bash mode automatically, matching typed ! behavior

This is useful for quick shell operations while maintaining conversation context.

Prompt suggestions

When you first open a session, a grayed-out example command appears in the prompt input to help you get started. Claude Code picks this from your project's git history, so it reflects files you've been working on recently.

After Claude responds, suggestions continue to appear based on your conversation history, such as a follow-up step from a multi-part request or a natural continuation of your workflow.

  • Press Tab or Right arrow to accept the suggestion, or press Enter to accept and submit
  • Start typing to dismiss it

The suggestion runs as a background request that reuses the parent conversation's prompt cache, so the additional cost is minimal. Claude Code skips suggestion generation when the cache is cold to avoid unnecessary cost.

Suggestions are automatically skipped after the first turn of a conversation, in non-interactive mode, and in plan mode.

To disable prompt suggestions entirely, set the environment variable or toggle the setting in /config:

export CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false

Side questions with /btw

Use /btw to ask a quick question about your current work without adding to the conversation history. This is useful when you want a fast answer but don't want to clutter the main context or derail Claude from a long-running task.

/btw what was the name of that config file again?

Side questions have full visibility into the current conversation, so you can ask about code Claude has already read, decisions it made earlier, or anything else from the session. The question and answer are ephemeral: they appear in a dismissible overlay and never enter the conversation history.

  • Available while Claude is working: you can run /btw even while Claude is processing a response. The side question runs independently and does not interrupt the main turn.
  • No tool access: side questions answer only from what is already in context. Claude cannot read files, run commands, or search when answering a side question.
  • Single response: there are no follow-up turns. If you need a back-and-forth, use a normal prompt instead.
  • Low cost: the side question reuses the parent conversation's prompt cache, so the additional cost is minimal.

Press Space, Enter, or Escape to dismiss the answer and return to the prompt.

/btw is the inverse of a subagent: it sees your full conversation but has no tools, while a subagent has full tools but starts with an empty context. Use /btw to ask about what Claude already knows from this session; use a subagent to go find out something new.

Task list

When working on complex, multi-step work, Claude creates a task list to track progress. Tasks appear in the status area of your terminal with indicators showing what's pending, in progress, or complete.

  • Press Ctrl+T to toggle the task list view. The display shows up to 10 tasks at a time
  • To see all tasks or clear them, ask Claude directly: "show me all tasks" or "clear all tasks"
  • Tasks persist across context compactions, helping Claude stay organized on larger projects
  • To share a task list across sessions, set CLAUDE_CODE_TASK_LIST_ID to use a named directory in ~/.claude/tasks/: CLAUDE_CODE_TASK_LIST_ID=my-project claude

PR review status

When working on a branch with an open pull request, Claude Code displays a clickable PR link in the footer (for example, "PR #446"). The link has a colored underline indicating the review state:

  • Green: approved
  • Yellow: pending review
  • Red: changes requested
  • Gray: draft
  • Purple: merged

Cmd+click (Mac) or Ctrl+click (Windows/Linux) the link to open the pull request in your browser. The status updates automatically every 60 seconds.

Note

PR status requires the gh CLI to be installed and authenticated (gh auth login).

See also

Claude Code automatically tracks Claude's file edits as you work, allowing you to quickly undo changes and rewind to previous states if anything gets off track.

How checkpoints work

As you work with Claude, checkpointing automatically captures the state of your code before each edit. This safety net lets you pursue ambitious, wide-scale tasks knowing you can always return to a prior code state.

Automatic tracking

Claude Code tracks all changes made by its file editing tools:

  • Every user prompt creates a new checkpoint
  • Checkpoints persist across sessions, so you can access them in resumed conversations
  • Automatically cleaned up along with sessions after 30 days (configurable)

Rewind and summarize

Press Esc twice (Esc + Esc) or use the /rewind command to open the rewind menu. A scrollable list shows each of your prompts from the session. Select the point you want to act on, then choose an action:

  • Restore code and conversation: revert both code and conversation to that point
  • Restore conversation: rewind to that message while keeping current code
  • Restore code: revert file changes while keeping the conversation
  • Summarize from here: compress the conversation from this point forward into a summary, freeing context window space
  • Never mind: return to the message list without making changes

After restoring the conversation or summarizing, the original prompt from the selected message is restored into the input field so you can re-send or edit it.

Restore vs. summarize

The three restore options revert state: they undo code changes, conversation history, or both. "Summarize from here" works differently:

  • Messages before the selected message stay intact
  • The selected message and all subsequent messages get replaced with a compact AI-generated summary
  • No files on disk are changed
  • The original messages are preserved in the session transcript, so Claude can reference the details if needed

This is similar to /compact, but targeted: instead of summarizing the entire conversation, you keep early context in full detail and only compress the parts that are using up space. You can type optional instructions to guide what the summary focuses on.

Note

Summarize keeps you in the same session and compresses context. If you want to branch off and try a different approach while preserving the original session intact, use fork instead (claude --continue --fork-session).

Common use cases

Checkpoints are particularly useful when:

  • Exploring alternatives: try different implementation approaches without losing your starting point
  • Recovering from mistakes: quickly undo changes that introduced bugs or broke functionality
  • Iterating on features: experiment with variations knowing you can revert to working states
  • Freeing context space: summarize a verbose debugging session from the midpoint forward, keeping your initial instructions intact

Limitations

Bash command changes not tracked

Checkpointing does not track files modified by bash commands. For example, if Claude Code runs:

rm file.txt
mv old.txt new.txt
cp source.txt dest.txt

These file modifications cannot be undone through rewind. Only direct file edits made through Claude's file editing tools are tracked.

External changes not tracked

Checkpointing only tracks files that have been edited within the current session. Manual changes you make to files outside of Claude Code and edits from other concurrent sessions are normally not captured, unless they happen to modify the same files as the current session.

Not a replacement for version control

Checkpoints are designed for quick, session-level recovery. For permanent version history and collaboration:

  • Continue using version control (ex. Git) for commits, branches, and long-term history
  • Checkpoints complement but don't replace proper version control
  • Think of checkpoints as "local undo" and Git as "permanent history"

See also

Tip

For a quickstart guide with examples, see Automate workflows with hooks.

Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks. If you're setting up hooks for the first time, start with the guide instead.

Hook lifecycle

Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Events fall into three cadences: once per session (SessionStart, SessionEnd), once per turn (UserPromptSubmit, Stop, StopFailure), and on every tool call inside the agentic loop (PreToolUse, PostToolUse):

Hook lifecycle diagram showing SessionStart, then a per-turn loop containing UserPromptSubmit, the nested agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCreated, TaskCompleted), and Stop or StopFailure, followed by TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution, PermissionDenied as a side branch from PermissionRequest for auto-mode denials, and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events

The table below summarizes when each event fires. The Hook events section documents the full input schema and decision control options for each one.

Event When it fires
SessionStart When a session begins or resumes
UserPromptSubmit When you submit a prompt, before Claude processes it
PreToolUse Before a tool call executes. Can block it
PermissionRequest When a permission dialog appears
PermissionDenied When a tool call is denied by the auto mode classifier. Return {retry: true} to tell the model it may retry the denied tool call
PostToolUse After a tool call succeeds
PostToolUseFailure After a tool call fails
Notification When Claude Code sends a notification
SubagentStart When a subagent is spawned
SubagentStop When a subagent finishes
TaskCreated When a task is being created via TaskCreate
TaskCompleted When a task is being marked as completed
Stop When Claude finishes responding
StopFailure When the turn ends due to an API error. Output and exit code are ignored
TeammateIdle When an agent team teammate is about to go idle
InstructionsLoaded When a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded during a session
ConfigChange When a configuration file changes during a session
CwdChanged When the working directory changes, for example when Claude executes a cd command. Useful for reactive environment management with tools like direnv
FileChanged When a watched file changes on disk. The matcher field specifies which filenames to watch
WorktreeCreate When a worktree is being created via --worktree or isolation: "worktree". Replaces default git behavior
WorktreeRemove When a worktree is being removed, either at session exit or when a subagent finishes
PreCompact Before context compaction
PostCompact After context compaction completes
Elicitation When an MCP server requests user input during a tool call
ElicitationResult After a user responds to an MCP elicitation, before the response is sent back to the server
SessionEnd When a session terminates

How a hook resolves

To see how these pieces fit together, consider this PreToolUse hook that blocks destructive shell commands. The matcher narrows to Bash tool calls and the if condition narrows further to commands starting with rm, so block-rm.sh only spawns when both filters match:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

The script reads the JSON input from stdin, extracts the command, and returns a permissionDecision of "deny" if it contains rm -rf:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0  # allow the command
fi

Now suppose Claude Code decides to run Bash "rm -rf /tmp/build". Here's what happens:

Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, if condition checks for Bash(rm *) match, hook handler runs, result returns to Claude Code
Event fires

The PreToolUse event fires. Claude Code sends the tool input as JSON on stdin to the hook:

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
Matcher checks

The matcher "Bash" matches the tool name, so this hook group activates. If you omit the matcher or use "*", the group activates on every occurrence of the event.

If condition checks

The if condition "Bash(rm *)" matches because the command starts with rm, so this handler spawns. If the command had been npm test, the if check would fail and block-rm.sh would never run, avoiding the process spawn overhead. The if field is optional; without it, every handler in the matched group runs.

Hook handler runs

The script inspects the full command and finds rm -rf, so it prints a decision to stdout:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook"
  }
}

If the command had been a safer rm variant like rm file.txt, the script would hit exit 0 instead, which tells Claude Code to allow the tool call with no further action.

Claude Code acts on the result

Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.

The Configuration section below documents the full schema, and each hook event section documents what input your command receives and what output it can return.

Configuration

Hooks are defined in JSON settings files. The configuration has three levels of nesting:

  1. Choose a hook event to respond to, like PreToolUse or Stop
  2. Add a matcher group to filter when it fires, like "only for the Bash tool"
  3. Define one or more hook handlers to run when matched

See How a hook resolves above for a complete walkthrough with an annotated example.

Note

This page uses specific terms for each level: hook event for the lifecycle point, matcher group for the filter, and hook handler for the shell command, HTTP endpoint, prompt, or agent that runs. "Hook" on its own refers to the general feature.

Hook locations

Where you define a hook determines its scope:

Location Scope Shareable
~/.claude/settings.json All your projects No, local to your machine
.claude/settings.json Single project Yes, can be committed to the repo
.claude/settings.local.json Single project No, gitignored
Managed policy settings Organization-wide Yes, admin-controlled
Plugin hooks/hooks.json When plugin is enabled Yes, bundled with the plugin
Skill or agent frontmatter While the component is active Yes, defined in the component file

For details on settings file resolution, see settings. Enterprise administrators can use allowManagedHooksOnly to block user, project, and plugin hooks. Hooks from plugins force-enabled in managed settings enabledPlugins are exempt, so administrators can distribute vetted hooks through an organization marketplace. See Hook configuration.

Matcher patterns

The matcher field filters when hooks fire. How a matcher is evaluated depends on the characters it contains:

Matcher value Evaluated as Example
"*", "", or omitted Match all fires on every occurrence of the event
Only letters, digits, _, and | Exact string, or |-separated list of exact strings Bash matches only the Bash tool; Edit|Write matches either tool exactly
Contains any other character JavaScript regular expression ^Notebook matches any tool starting with Notebook; mcp__memory__.* matches every tool from the memory server

The FileChanged event does not follow these rules when building its watch list. See FileChanged.

Each event type matches on a different field:

Event What the matcher filters Example matcher values
PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied tool name Bash, Edit|Write, mcp__.*
SessionStart how the session started startup, resume, clear, compact
SessionEnd why the session ended clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other
Notification notification type permission_prompt, idle_prompt, auth_success, elicitation_dialog
SubagentStart agent type Bash, Explore, Plan, or custom agent names
PreCompact, PostCompact what triggered compaction manual, auto
SubagentStop agent type same values as SubagentStart
ConfigChange configuration source user_settings, project_settings, local_settings, policy_settings, skills
CwdChanged no matcher support always fires on every directory change
FileChanged literal filenames to watch (see FileChanged) .envrc|.env
StopFailure error type rate_limit, authentication_failed, billing_error, invalid_request, server_error, max_output_tokens, unknown
InstructionsLoaded load reason session_start, nested_traversal, path_glob_match, include, compact
Elicitation MCP server name your configured MCP server names
ElicitationResult MCP server name same values as Elicitation
UserPromptSubmit, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove no matcher support always fires on every occurrence

The matcher runs against a field from the JSON input that Claude Code sends to your hook on stdin. For tool events, that field is tool_name. Each hook event section lists the full set of matcher values and the input schema for that event.

This example runs a linting script only when Claude writes or edits a file:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

UserPromptSubmit, Stop, TeammateIdle, TaskCreated, TaskCompleted, WorktreeCreate, WorktreeRemove, and CwdChanged don't support matchers and always fire on every occurrence. If you add a matcher field to these events, it is silently ignored.

For tool events, you can filter more narrowly by setting the if field on individual hook handlers. if uses permission rule syntax to match against the tool name and arguments together, so "Bash(git *)" runs only for git commands and "Edit(*.ts)" runs only for TypeScript files.

Match MCP tools

MCP server tools appear as regular tools in tool events (PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied), so you can match them the same way you match any other tool name.

MCP tools follow the naming pattern mcp__<server>__<tool>, for example:

  • mcp__memory__create_entities: Memory server's create entities tool
  • mcp__filesystem__read_file: Filesystem server's read file tool
  • mcp__github__search_repositories: GitHub server's search tool

To match every tool from a server, append .* to the server prefix. The .* is required: a matcher like mcp__memory contains only letters and underscores, so it is compared as an exact string and matches no tool.

  • mcp__memory__.* matches all tools from the memory server
  • mcp__.*__write.* matches any tool whose name starts with write from any server

This example logs all memory server operations and validates write operations from any MCP server:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Hook handler fields

Each object in the inner hooks array is a hook handler: the shell command, HTTP endpoint, LLM prompt, or agent that runs when the matcher matches. There are four types:

  • Command hooks (type: "command"): run a shell command. Your script receives the event's JSON input on stdin and communicates results back through exit codes and stdout.
  • HTTP hooks (type: "http"): send the event's JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same JSON output format as command hooks.
  • Prompt hooks (type: "prompt"): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See Prompt-based hooks.
  • Agent hooks (type: "agent"): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See Agent-based hooks.

Common fields

These fields apply to all hook types:

Field Required Description
type yes "command", "http", "prompt", or "agent"
if no Permission rule syntax to filter when this hook runs, such as "Bash(git *)" or "Edit(*.ts)". The hook only spawns if the tool call matches the pattern. Only evaluated on tool events: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, and PermissionDenied. On other events, a hook with if set never runs. Uses the same syntax as permission rules
timeout no Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent
statusMessage no Custom spinner message displayed while the hook runs
once no If true, runs only once per session then is removed. Skills only, not agents. See Hooks in skills and agents

Command hook fields

In addition to the common fields, command hooks accept these fields:

Field Required Description
command yes Shell command to execute
async no If true, runs in the background without blocking. See Run hooks in the background
asyncRewake no If true, runs in the background and wakes Claude on exit code 2. Implies async. The hook's stderr, or stdout if stderr is empty, is shown to Claude as a system reminder so it can react to a long-running background failure
shell no Shell to use for this hook. Accepts "bash" (default) or "powershell". Setting "powershell" runs the command via PowerShell on Windows. Does not require CLAUDE_CODE_USE_POWERSHELL_TOOL since hooks spawn PowerShell directly

HTTP hook fields

In addition to the common fields, HTTP hooks accept these fields:

Field Required Description
url yes URL to send the POST request to
headers no Additional HTTP headers as key-value pairs. Values support environment variable interpolation using $VAR_NAME or ${VAR_NAME} syntax. Only variables listed in allowedEnvVars are resolved
allowedEnvVars no List of environment variable names that may be interpolated into header values. References to unlisted variables are replaced with empty strings. Required for any env var interpolation to work

Claude Code sends the hook's JSON input as the POST request body with Content-Type: application/json. The response body uses the same JSON output format as command hooks.

Error handling differs from command hooks: non-2xx responses, connection failures, and timeouts all produce non-blocking errors that allow execution to continue. To block a tool call or deny a permission, return a 2xx response with a JSON body containing decision: "block" or a hookSpecificOutput with permissionDecision: "deny".

This example sends PreToolUse events to a local validation service, authenticating with a token from the MY_TOKEN environment variable:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

Prompt and agent hook fields

In addition to the common fields, prompt and agent hooks accept these fields:

Field Required Description
prompt yes Prompt text to send to the model. Use $ARGUMENTS as a placeholder for the hook input JSON
model no Model to use for evaluation. Defaults to a fast model

All matching hooks run in parallel, and identical handlers are deduplicated automatically. Command hooks are deduplicated by command string, and HTTP hooks are deduplicated by URL. Handlers run in the current directory with Claude Code's environment. The $CLAUDE_CODE_REMOTE environment variable is set to "true" in remote web environments and not set in the local CLI.

Reference scripts by path

Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:

  • $CLAUDE_PROJECT_DIR: the project root. Wrap in quotes to handle paths with spaces.
  • ${CLAUDE_PLUGIN_ROOT}: the plugin's installation directory, for scripts bundled with a plugin. Changes on each plugin update.
  • ${CLAUDE_PLUGIN_DATA}: the plugin's persistent data directory, for dependencies and state that should survive plugin updates.

This example uses $CLAUDE_PROJECT_DIR to run a style checker from the project's .claude/hooks/ directory after any Write or Edit tool call:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}

Hooks in skills and agents

In addition to settings files and plugins, hooks can be defined directly in skills and subagents using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.

All hook events are supported. For subagents, Stop hooks are automatically converted to SubagentStop since that is the event that fires when a subagent completes.

Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.

This skill defines a PreToolUse hook that runs a security validation script before each Bash command:

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

Agents use the same format in their YAML frontmatter.

The /hooks menu

Type /hooks in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hook's command, prompt, or URL.

The menu displays all four hook types: command, prompt, agent, and http. Each hook is labeled with a [type] prefix and a source indicating where it was defined:

  • User: from ~/.claude/settings.json
  • Project: from .claude/settings.json
  • Local: from .claude/settings.local.json
  • Plugin: from a plugin's hooks/hooks.json
  • Session: registered in memory for the current session
  • Built-in: registered internally by Claude Code

Selecting a hook opens a detail view showing its event, matcher, type, source file, and the full command, prompt, or URL. The menu is read-only: to add, modify, or remove hooks, edit the settings JSON directly or ask Claude to make the change.

Disable or remove hooks

To remove a hook, delete its entry from the settings JSON file.

To temporarily disable all hooks without removing them, set "disableAllHooks": true in your settings file. There is no way to disable an individual hook while keeping it in the configuration.

The disableAllHooks setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, disableAllHooks set in user, project, or local settings cannot disable those managed hooks. Only disableAllHooks set at the managed settings level can disable managed hooks.

Direct edits to hooks in settings files are normally picked up automatically by the file watcher.

Hook input and output

Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under Hook events includes its specific input schema and decision control options.

Common input fields

Hook events receive these fields as JSON, in addition to event-specific fields documented in each hook event section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.

Field Description
session_id Current session identifier
transcript_path Path to conversation JSON
cwd Current working directory when the hook is invoked
permission_mode Current permission mode: "default", "plan", "acceptEdits", "auto", "dontAsk", or "bypassPermissions". Not all events receive this field: see each event's JSON example below to check
hook_event_name Name of the event that fired

When running with --agent or inside a subagent, two additional fields are included:

Field Description
agent_id Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls.
agent_type Agent name (for example, "Explore" or "security-reviewer"). Present when the session uses --agent or the hook fires inside a subagent. For subagents, the subagent's type takes precedence over the session's --agent value.

For example, a PreToolUse hook for a Bash command receives this on stdin:

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

The tool_name and tool_input fields are event-specific. Each hook event section documents the additional fields for that event.

Exit code output

The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.

Exit 0 means success. Claude Code parses stdout for JSON output fields. JSON output is only processed on exit 0. For most events, stdout is written to the debug log but not shown in the transcript. The exceptions are UserPromptSubmit and SessionStart, where stdout is added as context that Claude can see and act on.

Exit 2 means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: PreToolUse blocks the tool call, UserPromptSubmit rejects the prompt, and so on. See exit code 2 behavior for the full list.

Any other exit code is a non-blocking error for most hook events. The transcript shows a <hook name> hook error notice followed by the first line of stderr, so you can identify the cause without --debug. Execution continues and the full stderr is written to the debug log.

For example, a hook command script that blocks dangerous Bash commands:

#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)

if [[ "$command" == rm* ]]; then
  echo "Blocked: rm commands are not allowed" >&2
  exit 2  # Blocking error: tool call is prevented
fi

exit 0  # Success: tool call proceeds

Warning

For most hook events, only exit code 2 blocks the action. Claude Code treats exit code 1 as a non-blocking error and proceeds with the action, even though 1 is the conventional Unix failure code. If your hook is meant to enforce a policy, use exit 2. The exception is WorktreeCreate, where any non-zero exit code aborts worktree creation.

Exit code 2 behavior per event

Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.

Hook event Can block? What happens on exit 2
PreToolUse Yes Blocks the tool call
PermissionRequest Yes Denies the permission
UserPromptSubmit Yes Blocks prompt processing and erases the prompt
Stop Yes Prevents Claude from stopping, continues the conversation
SubagentStop Yes Prevents the subagent from stopping
TeammateIdle Yes Prevents the teammate from going idle (teammate continues working)
TaskCreated Yes Rolls back the task creation
TaskCompleted Yes Prevents the task from being marked as completed
ConfigChange Yes Blocks the configuration change from taking effect (except policy_settings)
StopFailure No Output and exit code are ignored
PostToolUse No Shows stderr to Claude (tool already ran)
PostToolUseFailure No Shows stderr to Claude (tool already failed)
PermissionDenied No Exit code and stderr are ignored (denial already occurred). Use JSON hookSpecificOutput.retry: true to tell the model it may retry
Notification No Shows stderr to user only
SubagentStart No Shows stderr to user only
SessionStart No Shows stderr to user only
SessionEnd No Shows stderr to user only
CwdChanged No Shows stderr to user only
FileChanged No Shows stderr to user only
PreCompact Yes Blocks compaction
PostCompact No Shows stderr to user only
Elicitation Yes Denies the elicitation
ElicitationResult Yes Blocks the response (action becomes decline)
WorktreeCreate Yes Any non-zero exit code causes worktree creation to fail
WorktreeRemove No Failures are logged in debug mode only
InstructionsLoaded No Exit code is ignored

HTTP response handling

HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:

  • 2xx with an empty body: success, equivalent to exit code 0 with no output
  • 2xx with a plain text body: success, the text is added as context
  • 2xx with a JSON body: success, parsed using the same JSON output schema as command hooks
  • Non-2xx status: non-blocking error, execution continues
  • Connection failure or timeout: non-blocking error, execution continues

Unlike command hooks, HTTP hooks cannot signal a blocking error through status codes alone. To block a tool call or deny a permission, return a 2xx response with a JSON body containing the appropriate decision fields.

JSON output

Exit codes let you allow or block, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including decision control for blocking, allowing, or escalating to the user.

Note

You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.

Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See JSON validation failed in the troubleshooting guide.

Hook output injected into context (additionalContext, systemMessage, or plain stdout) is capped at 10,000 characters. Output that exceeds this limit is saved to a file and replaced with a preview and file path, the same way large tool results are handled.

The JSON object supports three kinds of fields:

  • Universal fields like continue work across all events. These are listed in the table below.
  • Top-level decision and reason are used by some events to block or provide feedback.
  • hookSpecificOutput is a nested object for events that need richer control. It requires a hookEventName field set to the event name.
Field Default Description
continue true If false, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields
stopReason none Message shown to the user when continue is false. Not shown to Claude
suppressOutput false If true, omits stdout from the debug log
systemMessage none Warning message shown to the user

To stop Claude entirely regardless of event type:

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }

Decision control

Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:

Events Decision pattern Key fields
UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange, PreCompact Top-level decision decision: "block", reason
TeammateIdle, TaskCreated, TaskCompleted Exit code or continue: false Exit code 2 blocks the action with stderr feedback. JSON {"continue": false, "stopReason": "..."} also stops the teammate entirely, matching Stop hook behavior
PreToolUse hookSpecificOutput permissionDecision (allow/deny/ask/defer), permissionDecisionReason
PermissionRequest hookSpecificOutput decision.behavior (allow/deny)
PermissionDenied hookSpecificOutput retry: true tells the model it may retry the denied tool call
WorktreeCreate path return Command hook prints path on stdout; HTTP hook returns hookSpecificOutput.worktreePath. Hook failure or missing path fails creation
Elicitation hookSpecificOutput action (accept/decline/cancel), content (form field values for accept)
ElicitationResult hookSpecificOutput action (accept/decline/cancel), content (form field values override)
WorktreeRemove, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged None No decision control. Used for side effects like logging or cleanup

Here are examples of each pattern in action:

Used by UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange, and PreCompact. The only value is "block". To allow the action to proceed, omit decision from your JSON, or exit 0 without any JSON at all:

{
  "decision": "block",
  "reason": "Test suite must pass before proceeding"
}

For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see What you can automate in the guide and the Bash command validator reference implementation.

Hook events

Each event corresponds to a point in Claude Code's lifecycle where hooks can run. The sections below are ordered to match the lifecycle: from session setup through the agentic loop to session end. Each section describes when the event fires, what matchers it supports, the JSON input it receives, and how to control behavior through output.

SessionStart

Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use CLAUDE.md instead.

SessionStart runs on every session, so keep these hooks fast. Only type: "command" hooks are supported.

The matcher value corresponds to how the session was initiated:

Matcher When it fires
startup New session
resume --resume, --continue, or /resume
clear /clear
compact Auto or manual compaction

SessionStart input

In addition to the common input fields, SessionStart hooks receive source, model, and optionally agent_type. The source field indicates how the session started: "startup" for new sessions, "resume" for resumed sessions, "clear" after /clear, or "compact" after compaction. The model field contains the model identifier. If you start Claude Code with claude --agent <name>, an agent_type field contains the agent name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-6"
}

SessionStart decision control

Any text your hook script prints to stdout is added as context for Claude. In addition to the JSON output fields available to all hooks, you can return these event-specific fields:

Field Description
additionalContext String added to Claude's context. Multiple hooks' values are concatenated
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "My additional context here"
  }
}

Persist environment variables

SessionStart hooks have access to the CLAUDE_ENV_FILE environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.

To set individual environment variables, write export statements to CLAUDE_ENV_FILE. Use append (>>) to preserve variables set by other hooks:

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0

To capture all environment changes from setup commands, compare the exported variables before and after:

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.

Note

CLAUDE_ENV_FILE is available for SessionStart, CwdChanged, and FileChanged hooks. Other hook types do not have access to this variable.

InstructionsLoaded

Fires when a CLAUDE.md or .claude/rules/*.md file is loaded into context. This event fires at session start for eagerly-loaded files and again later when files are lazily loaded, for example when Claude accesses a subdirectory that contains a nested CLAUDE.md or when conditional rules with paths: frontmatter match. The hook does not support blocking or decision control. It runs asynchronously for observability purposes.

The matcher runs against load_reason. For example, use "matcher": "session_start" to fire only for files loaded at session start, or "matcher": "path_glob_match|nested_traversal" to fire only for lazy loads.

InstructionsLoaded input

In addition to the common input fields, InstructionsLoaded hooks receive these fields:

Field Description
file_path Absolute path to the instruction file that was loaded
memory_type Scope of the file: "User", "Project", "Local", or "Managed"
load_reason Why the file was loaded: "session_start", "nested_traversal", "path_glob_match", "include", or "compact". The "compact" value fires when instruction files are re-loaded after a compaction event
globs Path glob patterns from the file's paths: frontmatter, if any. Present only for path_glob_match loads
trigger_file_path Path to the file whose access triggered this load, for lazy loads
parent_file_path Path to the parent instruction file that included this one, for include loads
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

InstructionsLoaded decision control

InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.

UserPromptSubmit

Runs when the user submits a prompt, before Claude processes it. This allows you to add additional context based on the prompt/conversation, validate prompts, or block certain types of prompts.

UserPromptSubmit input

In addition to the common input fields, UserPromptSubmit hooks receive the prompt field containing the text the user submitted.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}

UserPromptSubmit decision control

UserPromptSubmit hooks can control whether a user prompt is processed and add context. All JSON output fields are available.

There are two ways to add context to the conversation on exit code 0:

  • Plain text stdout: any non-JSON text written to stdout is added as context
  • JSON with additionalContext: use the JSON format below for more control. The additionalContext field is added as context

Plain stdout is shown as hook output in the transcript. The additionalContext field is added more discretely.

To block a prompt, return a JSON object with decision set to "block":

Field Description
decision "block" prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed
reason Shown to the user when decision is "block". Not added to context
additionalContext String added to Claude's context
sessionTitle Sets the session title, same effect as /rename. Use to name sessions automatically based on the prompt content
{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title"
  }
}

Note

The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to block prompts or want more structured control.

PreToolUse

Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: Bash, Edit, Write, Read, Glob, Grep, Agent, WebFetch, WebSearch, AskUserQuestion, ExitPlanMode, and any MCP tool names.

Use PreToolUse decision control to allow, deny, ask, or defer the tool call.

PreToolUse input

In addition to the common input fields, PreToolUse hooks receive tool_name, tool_input, and tool_use_id. The tool_input fields depend on the tool:

Bash

Executes shell commands.

Field Type Example Description
command string "npm test" The shell command to execute
description string "Run test suite" Optional description of what the command does
timeout number 120000 Optional timeout in milliseconds
run_in_background boolean false Whether to run the command in background
Write

Creates or overwrites a file.

Field Type Example Description
file_path string "/path/to/file.txt" Absolute path to the file to write
content string "file content" Content to write to the file
Edit

Replaces a string in an existing file.

Field Type Example Description
file_path string "/path/to/file.txt" Absolute path to the file to edit
old_string string "original text" Text to find and replace
new_string string "replacement text" Replacement text
replace_all boolean false Whether to replace all occurrences
Read

Reads file contents.

Field Type Example Description
file_path string "/path/to/file.txt" Absolute path to the file to read
offset number 10 Optional line number to start reading from
limit number 50 Optional number of lines to read
Glob

Finds files matching a glob pattern.

Field Type Example Description
pattern string "**/*.ts" Glob pattern to match files against
path string "/path/to/dir" Optional directory to search in. Defaults to current working directory
Grep

Searches file contents with regular expressions.

Field Type Example Description
pattern string "TODO.*fix" Regular expression pattern to search for
path string "/path/to/dir" Optional file or directory to search in
glob string "*.ts" Optional glob pattern to filter files
output_mode string "content" "content", "files_with_matches", or "count". Defaults to "files_with_matches"
-i boolean true Case insensitive search
multiline boolean false Enable multiline matching
WebFetch

Fetches and processes web content.

Field Type Example Description
url string "https://example.com/api" URL to fetch content from
prompt string "Extract the API endpoints" Prompt to run on the fetched content
WebSearch

Searches the web.

Field Type Example Description
query string "react hooks best practices" Search query
allowed_domains array ["docs.example.com"] Optional: only include results from these domains
blocked_domains array ["spam.example.com"] Optional: exclude results from these domains
Agent

Spawns a subagent.

Field Type Example Description
prompt string "Find all API endpoints" The task for the agent to perform
description string "Find API endpoints" Short description of the task
subagent_type string "Explore" Type of specialized agent to use
model string "sonnet" Optional model alias to override the default
AskUserQuestion

Asks the user one to four multiple-choice questions.

Field Type Example Description
questions array [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}] Questions to present, each with a question string, short header, options array, and optional multiSelect flag
answers object {"Which framework?": "React"} Optional. Maps question text to the selected option label. Multi-select answers join labels with commas. Claude does not set this field; supply it via updatedInput to answer programmatically

PreToolUse decision control

PreToolUse hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level decision field, PreToolUse returns its decision inside a hookSpecificOutput object. This gives it richer control: four outcomes (allow, deny, ask, or defer) plus the ability to modify tool input before execution.

Field Description
permissionDecision "allow" skips the permission prompt. "deny" prevents the tool call. "ask" prompts the user to confirm. "defer" exits gracefully so the tool can be resumed later. Deny and ask rules are still evaluated regardless of what the hook returns
permissionDecisionReason For "allow" and "ask", shown to the user but not Claude. For "deny", shown to Claude. For "defer", ignored
updatedInput Modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with "allow" to auto-approve, or "ask" to show the modified input to the user. For "defer", ignored
additionalContext String added to Claude's context before the tool executes. For "defer", ignored

When multiple PreToolUse hooks return different decisions, precedence is deny > defer > ask > allow.

When a hook returns "ask", the permission prompt displayed to the user includes a label identifying where the hook came from: for example, [User], [Project], [Plugin], or [Local]. This helps users understand which configuration source is requesting confirmation.

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}

AskUserQuestion and ExitPlanMode require user interaction and normally block in non-interactive mode with the -p flag. Returning permissionDecision: "allow" together with updatedInput satisfies that requirement: the hook reads the tool's input from stdin, collects the answer through your own UI, and returns it in updatedInput so the tool runs without prompting. Returning "allow" alone is not sufficient for these tools. For AskUserQuestion, echo back the original questions array and add an answers object mapping each question's text to the chosen answer.

Note

PreToolUse previously used top-level decision and reason fields, but these are deprecated for this event. Use hookSpecificOutput.permissionDecision and hookSpecificOutput.permissionDecisionReason instead. The deprecated values "approve" and "block" map to "allow" and "deny" respectively. Other events like PostToolUse and Stop continue to use top-level decision and reason as their current format.

Defer a tool call for later

"defer" is for integrations that run claude -p as a subprocess and read its JSON output, such as an Agent SDK app or a custom UI built on top of Claude Code. It lets that calling process pause Claude at a tool call, collect input through its own interface, and resume where it left off. Claude Code honors this value only in non-interactive mode with the -p flag. In interactive sessions it logs a warning and ignores the hook result.

Note

The defer value requires Claude Code v2.1.89 or later. Earlier versions do not recognize it and the tool proceeds through the normal permission flow.

The AskUserQuestion tool is the typical case: Claude wants to ask the user something, but there is no terminal to answer in. The round trip works like this:

  1. Claude calls AskUserQuestion. The PreToolUse hook fires.
  2. The hook returns permissionDecision: "defer". The tool does not execute. The process exits with stop_reason: "tool_deferred" and the pending tool call preserved in the transcript.
  3. The calling process reads deferred_tool_use from the SDK result, surfaces the question in its own UI, and waits for an answer.
  4. The calling process runs claude -p --resume <session-id>. The same tool call fires PreToolUse again.
  5. The hook returns permissionDecision: "allow" with the answer in updatedInput. The tool executes and Claude continues.

The deferred_tool_use field carries the tool's id, name, and input. The input is the parameters Claude generated for the tool call, captured before execution:

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
  }
}

There is no timeout or retry limit. The session remains on disk until you resume it. If the answer is not ready when you resume, the hook can return "defer" again and the process exits the same way. The calling process controls when to break the loop by eventually returning "allow" or "deny" from the hook.

"defer" only works when Claude makes a single tool call in the turn. If Claude makes several tool calls at once, "defer" is ignored with a warning and the tool proceeds through the normal permission flow. The constraint exists because resume can only re-run one tool: there is no way to defer one call from a batch without leaving the others unresolved.

If the deferred tool is no longer available when you resume, the process exits with stop_reason: "tool_deferred_unavailable" and is_error: true before the hook fires. This happens when an MCP server that provided the tool is not connected for the resumed session. The deferred_tool_use payload is still included so you can identify which tool went missing.

Warning

--resume does not restore the permission mode from the prior session. Pass the same --permission-mode flag on resume that was active when the tool was deferred. Claude Code logs a warning if the modes differ.

PermissionRequest

Runs when the user is shown a permission dialog. Use PermissionRequest decision control to allow or deny on behalf of the user.

Matches on tool name, same values as PreToolUse.

PermissionRequest input

PermissionRequest hooks receive tool_name and tool_input fields like PreToolUse hooks, but without tool_use_id. An optional permission_suggestions array contains the "always allow" options the user would normally see in the permission dialog. The difference is when the hook fires: PermissionRequest hooks run when a permission dialog is about to be shown to the user, while PreToolUse hooks run before tool execution regardless of permission status.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}

PermissionRequest decision control

PermissionRequest hooks can allow or deny permission requests. In addition to the JSON output fields available to all hooks, your hook script can return a decision object with these event-specific fields:

Field Description
behavior "allow" grants the permission, "deny" denies it
updatedInput For "allow" only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones
updatedPermissions For "allow" only: array of permission update entries to apply, such as adding an allow rule or changing the session permission mode
message For "deny" only: tells Claude why the permission was denied
interrupt For "deny" only: if true, stops Claude
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

Permission update entries

The updatedPermissions output field and the permission_suggestions input field both use the same array of entry objects. Each entry has a type that determines its other fields, and a destination that controls where the change is written.

type Fields Effect
addRules rules, behavior, destination Adds permission rules. rules is an array of {toolName, ruleContent?} objects. Omit ruleContent to match the whole tool. behavior is "allow", "deny", or "ask"
replaceRules rules, behavior, destination Replaces all rules of the given behavior at the destination with the provided rules
removeRules rules, behavior, destination Removes matching rules of the given behavior
setMode mode, destination Changes the permission mode. Valid modes are default, acceptEdits, dontAsk, bypassPermissions, and plan
addDirectories directories, destination Adds working directories. directories is an array of path strings
removeDirectories directories, destination Removes working directories

The destination field on every entry determines whether the change stays in memory or persists to a settings file.

destination Writes to
session in-memory only, discarded when the session ends
localSettings .claude/settings.local.json
projectSettings .claude/settings.json
userSettings ~/.claude/settings.json

A hook can echo one of the permission_suggestions it received as its own updatedPermissions output, which is equivalent to the user selecting that "always allow" option in the dialog.

PostToolUse

Runs immediately after a tool completes successfully.

Matches on tool name, same values as PreToolUse.

PostToolUse input

PostToolUse hooks fire after a tool has already executed successfully. The input includes both tool_input, the arguments sent to the tool, and tool_response, the result it returned. The exact schema for both depends on the tool.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "success": true
  },
  "tool_use_id": "toolu_01ABC123..."
}

PostToolUse decision control

PostToolUse hooks can provide feedback to Claude after tool execution. In addition to the JSON output fields available to all hooks, your hook script can return these event-specific fields:

Field Description
decision "block" prompts Claude with the reason. Omit to allow the action to proceed
reason Explanation shown to Claude when decision is "block"
additionalContext Additional context for Claude to consider
updatedMCPToolOutput For MCP tools only: replaces the tool's output with the provided value
{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude"
  }
}

PostToolUseFailure

Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.

Matches on tool name, same values as PreToolUse.

PostToolUseFailure input

PostToolUseFailure hooks receive the same tool_name and tool_input fields as PostToolUse, along with error information as top-level fields:

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false
}
Field Description
error String describing what went wrong
is_interrupt Optional boolean indicating whether the failure was caused by user interruption

PostToolUseFailure decision control

PostToolUseFailure hooks can provide context to Claude after a tool failure. In addition to the JSON output fields available to all hooks, your hook script can return these event-specific fields:

Field Description
additionalContext Additional context for Claude to consider alongside the error
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PermissionDenied

Runs when the auto mode classifier denies a tool call. This hook only fires in auto mode: it does not run when you manually deny a permission dialog, when a PreToolUse hook blocks a call, or when a deny rule matches. Use it to log classifier denials, adjust configuration, or tell the model it may retry the tool call.

Matches on tool name, same values as PreToolUse.

PermissionDenied input

In addition to the common input fields, PermissionDenied hooks receive tool_name, tool_input, tool_use_id, and reason.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "Auto mode denied: command targets a path outside the project"
}
Field Description
reason The classifier's explanation for why the tool call was denied

PermissionDenied decision control

PermissionDenied hooks can tell the model it may retry the denied tool call. Return a JSON object with hookSpecificOutput.retry set to true:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

When retry is true, Claude Code adds a message to the conversation telling the model it may retry the tool call. The denial itself is not reversed. If your hook does not return JSON, or returns retry: false, the denial stands and the model receives the original rejection message.

Notification

Runs when Claude Code sends notifications. Matches on notification type: permission_prompt, idle_prompt, auth_success, elicitation_dialog. Omit the matcher to run hooks for all notification types.

Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}

Notification input

In addition to the common input fields, Notification hooks receive message with the notification text, an optional title, and notification_type indicating which type fired.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission to use Bash",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

Notification hooks cannot block or modify notifications. In addition to the JSON output fields available to all hooks, you can return additionalContext to add context to the conversation:

Field Description
additionalContext String added to Claude's context

SubagentStart

Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like Bash, Explore, Plan, or custom agent names from .claude/agents/).

SubagentStart input

In addition to the common input fields, SubagentStart hooks receive agent_id with the unique identifier for the subagent and agent_type with the agent name (built-in agents like "Bash", "Explore", "Plan", or custom agent names).

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}

SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the JSON output fields available to all hooks, you can return:

Field Description
additionalContext String added to the subagent's context
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

SubagentStop

Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.

SubagentStop input

In addition to the common input fields, SubagentStop hooks receive stop_hook_active, agent_id, agent_type, agent_transcript_path, and last_assistant_message. The agent_type field is the value used for matcher filtering. The transcript_path is the main session's transcript, while agent_transcript_path is the subagent's own transcript stored in a nested subagents/ folder. The last_assistant_message field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues..."
}

SubagentStop hooks use the same decision control format as Stop hooks.

TaskCreated

Runs when a task is being created via the TaskCreate tool. Use this to enforce naming conventions, require task descriptions, or prevent certain tasks from being created.

When a TaskCreated hook exits with code 2, the task is not created and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with {"continue": false, "stopReason": "..."}. TaskCreated hooks do not support matchers and fire on every occurrence.

TaskCreated input

In addition to the common input fields, TaskCreated hooks receive task_id, task_subject, and optionally task_description, teammate_name, and team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCreated",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "my-project"
}
Field Description
task_id Identifier of the task being created
task_subject Title of the task
task_description Detailed description of the task. May be absent
teammate_name Name of the teammate creating the task. May be absent
team_name Name of the team. May be absent

TaskCreated decision control

TaskCreated hooks support two ways to control task creation:

  • Exit code 2: the task is not created and the stderr message is fed back to the model as feedback.
  • JSON {"continue": false, "stopReason": "..."}: stops the teammate entirely, matching Stop hook behavior. The stopReason is shown to the user.

This example blocks tasks whose subjects don't follow the required format:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
  echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
  exit 2
fi

exit 0

TaskCompleted

Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an agent team teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.

When a TaskCompleted hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with {"continue": false, "stopReason": "..."}. TaskCompleted hooks do not support matchers and fire on every occurrence.

TaskCompleted input

In addition to the common input fields, TaskCompleted hooks receive task_id, task_subject, and optionally task_description, teammate_name, and team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "my-project"
}
Field Description
task_id Identifier of the task being completed
task_subject Title of the task
task_description Detailed description of the task. May be absent
teammate_name Name of the teammate completing the task. May be absent
team_name Name of the team. May be absent

TaskCompleted decision control

TaskCompleted hooks support two ways to control task completion:

  • Exit code 2: the task is not marked as completed and the stderr message is fed back to the model as feedback.
  • JSON {"continue": false, "stopReason": "..."}: stops the teammate entirely, matching Stop hook behavior. The stopReason is shown to the user.

This example runs tests and blocks task completion if they fail:

#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')

# Run the test suite
if ! npm test 2>&1; then
  echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
  exit 2
fi

exit 0

Stop

Runs when the main Claude Code agent has finished responding. Does not run if the stoppage occurred due to a user interrupt. API errors fire StopFailure instead.

Stop input

In addition to the common input fields, Stop hooks receive stop_hook_active and last_assistant_message. The stop_hook_active field is true when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The last_assistant_message field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Stop",
  "stop_hook_active": true,
  "last_assistant_message": "I've completed the refactoring. Here's a summary..."
}

Stop decision control

Stop and SubagentStop hooks can control whether Claude continues. In addition to the JSON output fields available to all hooks, your hook script can return these event-specific fields:

Field Description
decision "block" prevents Claude from stopping. Omit to allow Claude to stop
reason Required when decision is "block". Tells Claude why it should continue
{
  "decision": "block",
  "reason": "Must be provided when Claude is blocked from stopping"
}

StopFailure

Runs instead of Stop when the turn ends due to an API error. Output and exit code are ignored. Use this to log failures, send alerts, or take recovery actions when Claude cannot complete a response due to rate limits, authentication problems, or other API errors.

StopFailure input

In addition to the common input fields, StopFailure hooks receive error, optional error_details, and optional last_assistant_message. The error field identifies the error type and is used for matcher filtering.

Field Description
error Error type: rate_limit, authentication_failed, billing_error, invalid_request, server_error, max_output_tokens, or unknown
error_details Additional details about the error, when available
last_assistant_message The rendered error text shown in the conversation. Unlike Stop and SubagentStop, where this field holds Claude's conversational output, for StopFailure it contains the API error string itself, such as "API Error: Rate limit reached"
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "StopFailure",
  "error": "rate_limit",
  "error_details": "429 Too Many Requests",
  "last_assistant_message": "API Error: Rate limit reached"
}

StopFailure hooks have no decision control. They run for notification and logging purposes only.

TeammateIdle

Runs when an agent team teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.

When a TeammateIdle hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. To stop the teammate entirely instead of re-running it, return JSON with {"continue": false, "stopReason": "..."}. TeammateIdle hooks do not support matchers and fire on every occurrence.

TeammateIdle input

In addition to the common input fields, TeammateIdle hooks receive teammate_name and team_name.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "my-project"
}
Field Description
teammate_name Name of the teammate that is about to go idle
team_name Name of the team

TeammateIdle decision control

TeammateIdle hooks support two ways to control teammate behavior:

  • Exit code 2: the teammate receives the stderr message as feedback and continues working instead of going idle.
  • JSON {"continue": false, "stopReason": "..."}: stops the teammate entirely, matching Stop hook behavior. The stopReason is shown to the user.

This example checks that a build artifact exists before allowing a teammate to go idle:

#!/bin/bash

if [ ! -f "./dist/output.js" ]; then
  echo "Build artifact missing. Run the build before stopping." >&2
  exit 2
fi

exit 0

ConfigChange

Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.

ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The source field in the input tells you which type of configuration changed, and the optional file_path field provides the path to the changed file.

The matcher filters on the configuration source:

Matcher When it fires
user_settings ~/.claude/settings.json changes
project_settings .claude/settings.json changes
local_settings .claude/settings.local.json changes
policy_settings Managed policy settings change
skills A skill file in .claude/skills/ changes

This example logs all configuration changes for security auditing:

{
  "hooks": {
    "ConfigChange": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
          }
        ]
      }
    ]
  }
}

ConfigChange input

In addition to the common input fields, ConfigChange hooks receive source and optionally file_path. The source field indicates which configuration type changed, and file_path provides the path to the specific file that was modified.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "ConfigChange",
  "source": "project_settings",
  "file_path": "/Users/.../my-project/.claude/settings.json"
}

ConfigChange decision control

ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON decision to prevent the change. When blocked, the new settings are not applied to the running session.

Field Description
decision "block" prevents the configuration change from being applied. Omit to allow the change
reason Explanation shown to the user when decision is "block"
{
  "decision": "block",
  "reason": "Configuration changes to project settings require admin approval"
}

policy_settings changes cannot be blocked. Hooks still fire for policy_settings sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.

CwdChanged

Runs when the working directory changes during a session, for example when Claude executes a cd command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with FileChanged for tools like direnv that manage per-directory environment.

CwdChanged hooks have access to CLAUDE_ENV_FILE. Variables written to that file persist into subsequent Bash commands for the session, just as in SessionStart hooks. Only type: "command" hooks are supported.

CwdChanged does not support matchers and fires on every directory change.

CwdChanged input

In addition to the common input fields, CwdChanged hooks receive old_cwd and new_cwd.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project/src",
  "hook_event_name": "CwdChanged",
  "old_cwd": "/Users/my-project",
  "new_cwd": "/Users/my-project/src"
}

CwdChanged output

In addition to the JSON output fields available to all hooks, CwdChanged hooks can return watchPaths to dynamically set which file paths FileChanged watches:

Field Description
watchPaths Array of absolute paths. Replaces the current dynamic watch list (paths from your matcher configuration are always watched). Returning an empty array clears the dynamic list, which is typical when entering a new directory

CwdChanged hooks have no decision control. They cannot block the directory change.

FileChanged

Runs when a watched file changes on disk. Useful for reloading environment variables when project configuration files are modified.

The matcher for this event serves two roles:

  • Build the watch list: the value is split on | and each segment is registered as a literal filename in the working directory, so ".envrc|.env" watches exactly those two files. Regex patterns are not useful here: a value like ^\.env would watch a file literally named ^\.env.
  • Filter which hooks run: when a watched file changes, the same value filters which hook groups run using the standard matcher rules against the changed file's basename.

FileChanged hooks have access to CLAUDE_ENV_FILE. Variables written to that file persist into subsequent Bash commands for the session, just as in SessionStart hooks. Only type: "command" hooks are supported.

FileChanged input

In addition to the common input fields, FileChanged hooks receive file_path and event.

Field Description
file_path Absolute path to the file that changed
event What happened: "change" (file modified), "add" (file created), or "unlink" (file deleted)
{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "FileChanged",
  "file_path": "/Users/my-project/.envrc",
  "event": "change"
}

FileChanged output

In addition to the JSON output fields available to all hooks, FileChanged hooks can return watchPaths to dynamically update which file paths are watched:

Field Description
watchPaths Array of absolute paths. Replaces the current dynamic watch list (paths from your matcher configuration are always watched). Use this when your hook script discovers additional files to watch based on the changed file

FileChanged hooks have no decision control. They cannot block the file change from occurring.

WorktreeCreate

When you run claude --worktree or a subagent uses isolation: "worktree", Claude Code creates an isolated working copy using git worktree. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.

Because the hook replaces the default behavior entirely, .worktreeinclude is not processed. If you need to copy local configuration files like .env into the new worktree, do it inside your hook script.

The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via hookSpecificOutput.worktreePath.

This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
          }
        ]
      }
    ]
  }
}

The hook reads the worktree name from the JSON input on stdin, checks out a fresh copy into a new directory, and prints the directory path. The echo on the last line is what Claude Code reads as the worktree path. Redirect any other output to stderr so it doesn't interfere with the path.

WorktreeCreate input

In addition to the common input fields, WorktreeCreate hooks receive the name field. This is a slug identifier for the new worktree, either specified by the user or auto-generated (for example, bold-oak-a3f2).

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeCreate",
  "name": "feature-auth"
}

WorktreeCreate output

WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. The hook must return the absolute path to the created worktree directory:

  • Command hooks (type: "command"): print the path on stdout.
  • HTTP hooks (type: "http"): return { "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } } in the response body.

If the hook fails or produces no path, worktree creation fails with an error.

WorktreeRemove

The cleanup counterpart to WorktreeCreate. This hook fires when a worktree is being removed, either when you exit a --worktree session and choose to remove it, or when a subagent with isolation: "worktree" finishes. For git-based worktrees, Claude handles cleanup automatically with git worktree remove. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.

Claude Code passes the path returned by WorktreeCreate as worktree_path in the hook input. This example reads that path and removes the directory:

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
          }
        ]
      }
    ]
  }
}

WorktreeRemove input

In addition to the common input fields, WorktreeRemove hooks receive the worktree_path field, which is the absolute path to the worktree being removed.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "WorktreeRemove",
  "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}

WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only.

PreCompact

Runs before Claude Code is about to run a compact operation.

The matcher value indicates whether compaction was triggered manually or automatically:

Matcher When it fires
manual /compact
auto Auto-compact when the context window is full

Exit with code 2 to block compaction. For a manual /compact, the stderr message is shown to the user. You can also block by returning JSON with "decision": "block".

Blocking automatic compaction has different effects depending on when it fires. If compaction was triggered proactively before the context limit, Claude Code skips it and the conversation continues uncompacted. If compaction was triggered to recover from a context-limit error already returned by the API, the underlying error surfaces and the current request fails.

PreCompact input

In addition to the common input fields, PreCompact hooks receive trigger and custom_instructions. For manual, custom_instructions contains what the user passes into /compact. For auto, custom_instructions is empty.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PreCompact",
  "trigger": "manual",
  "custom_instructions": ""
}

PostCompact

Runs after Claude Code completes a compact operation. Use this event to react to the new compacted state, for example to log the generated summary or update external state.

The same matcher values apply as for PreCompact:

Matcher When it fires
manual After /compact
auto After auto-compact when the context window is full

PostCompact input

In addition to the common input fields, PostCompact hooks receive trigger and compact_summary. The compact_summary field contains the conversation summary generated by the compact operation.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "PostCompact",
  "trigger": "manual",
  "compact_summary": "Summary of the compacted conversation..."
}

PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.

SessionEnd

Runs when a Claude Code session ends. Useful for cleanup tasks, logging session statistics, or saving session state. Supports matchers to filter by exit reason.

The reason field in the hook input indicates why the session ended:

Reason Description
clear Session cleared with /clear command
resume Session switched via interactive /resume
logout User logged out
prompt_input_exit User exited while prompt input was visible
bypass_permissions_disabled Bypass permissions mode was disabled
other Other exit reasons

SessionEnd input

In addition to the common input fields, SessionEnd hooks receive a reason field indicating why the session ended. See the reason table above for all values.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionEnd",
  "reason": "other"
}

SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.

SessionEnd hooks have a default timeout of 1.5 seconds. This applies to session exit, /clear, and switching sessions via interactive /resume. If a hook needs more time, set a per-hook timeout in the hook configuration. The overall budget is automatically raised to the highest per-hook timeout configured in settings files, up to 60 seconds. Timeouts set on plugin-provided hooks do not raise the budget. To override the budget explicitly, set the CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS environment variable in milliseconds.

CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude

Elicitation

Runs when an MCP server requests user input mid-task. By default, Claude Code shows an interactive dialog for the user to respond. Hooks can intercept this request and respond programmatically, skipping the dialog entirely.

The matcher field matches against the MCP server name.

Elicitation input

In addition to the common input fields, Elicitation hooks receive mcp_server_name, message, and optional mode, url, elicitation_id, and requested_schema fields.

For form-mode elicitation (the most common case):

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please provide your credentials",
  "mode": "form",
  "requested_schema": {
    "type": "object",
    "properties": {
      "username": { "type": "string", "title": "Username" }
    }
  }
}

For URL-mode elicitation (browser-based authentication):

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "Elicitation",
  "mcp_server_name": "my-mcp-server",
  "message": "Please authenticate",
  "mode": "url",
  "url": "https://auth.example.com/login"
}

Elicitation output

To respond programmatically without showing the dialog, return a JSON object with hookSpecificOutput:

{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept",
    "content": {
      "username": "alice"
    }
  }
}
Field Values Description
action accept, decline, cancel Whether to accept, decline, or cancel the request
content object Form field values to submit. Only used when action is accept

Exit code 2 denies the elicitation and shows stderr to the user.

ElicitationResult

Runs after a user responds to an MCP elicitation. Hooks can observe, modify, or block the response before it is sent back to the MCP server.

The matcher field matches against the MCP server name.

ElicitationResult input

In addition to the common input fields, ElicitationResult hooks receive mcp_server_name, action, and optional mode, elicitation_id, and content fields.

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "ElicitationResult",
  "mcp_server_name": "my-mcp-server",
  "action": "accept",
  "content": { "username": "alice" },
  "mode": "form",
  "elicitation_id": "elicit-123"
}

ElicitationResult output

To override the user's response, return a JSON object with hookSpecificOutput:

{
  "hookSpecificOutput": {
    "hookEventName": "ElicitationResult",
    "action": "decline",
    "content": {}
  }
}
Field Values Description
action accept, decline, cancel Overrides the user's action
content object Overrides form field values. Only meaningful when action is accept

Exit code 2 blocks the response, changing the effective action to decline.

Prompt-based hooks

In addition to command and HTTP hooks, Claude Code supports prompt-based hooks (type: "prompt") that use an LLM to evaluate whether to allow or block an action, and agent hooks (type: "agent") that spawn an agentic verifier with tool access. Not all events support every hook type.

Events that support all four hook types (command, http, prompt, and agent):

  • PermissionRequest
  • PostToolUse
  • PostToolUseFailure
  • PreToolUse
  • Stop
  • SubagentStop
  • TaskCompleted
  • TaskCreated
  • UserPromptSubmit

Events that support command and http hooks but not prompt or agent:

  • ConfigChange
  • CwdChanged
  • Elicitation
  • ElicitationResult
  • FileChanged
  • InstructionsLoaded
  • Notification
  • PermissionDenied
  • PostCompact
  • PreCompact
  • SessionEnd
  • StopFailure
  • SubagentStart
  • TeammateIdle
  • WorktreeCreate
  • WorktreeRemove

SessionStart supports only command hooks.

How prompt-based hooks work

Instead of executing a Bash command, prompt-based hooks:

  1. Send the hook input and your prompt to a Claude model, Haiku by default
  2. The LLM responds with structured JSON containing a decision
  3. Claude Code processes the decision automatically

Prompt hook configuration

Set type to "prompt" and provide a prompt string instead of a command. Use the $ARGUMENTS placeholder to inject the hook's JSON input data into your prompt text. Claude Code sends the combined prompt and input to a fast Claude model, which returns a JSON decision.

This Stop hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
          }
        ]
      }
    ]
  }
}
Field Required Description
type yes Must be "prompt"
prompt yes The prompt text to send to the LLM. Use $ARGUMENTS as a placeholder for the hook input JSON. If $ARGUMENTS is not present, input JSON is appended to the prompt
model no Model to use for evaluation. Defaults to a fast model
timeout no Timeout in seconds. Default: 30

Response schema

The LLM must respond with JSON containing:

{
  "ok": true | false,
  "reason": "Explanation for the decision"
}
Field Description
ok true allows the action, false prevents it
reason Required when ok is false. Explanation shown to Claude

Example: Multi-criteria Stop hook

This Stop hook uses a detailed prompt to check three conditions before allowing Claude to stop. If "ok" is false, Claude continues working with the provided reason as its next instruction. SubagentStop hooks use the same format to evaluate whether a subagent should stop:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Agent-based hooks

Agent-based hooks (type: "agent") are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.

How agent hooks work

When an agent hook fires:

  1. Claude Code spawns a subagent with your prompt and the hook's JSON input
  2. The subagent can use tools like Read, Grep, and Glob to investigate
  3. After up to 50 turns, the subagent returns a structured { "ok": true/false } decision
  4. Claude Code processes the decision the same way as a prompt hook

Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.

Agent hook configuration

Set type to "agent" and provide a prompt string. The configuration fields are the same as prompt hooks, with a longer default timeout:

Field Required Description
type yes Must be "agent"
prompt yes Prompt describing what to verify. Use $ARGUMENTS as a placeholder for the hook input JSON
model no Model to use. Defaults to a fast model
timeout no Timeout in seconds. Default: 60

The response schema is the same as prompt hooks: { "ok": true } to allow or { "ok": false, "reason": "..." } to block.

This Stop hook verifies that all unit tests pass before allowing Claude to finish:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

Run hooks in the background

By default, hooks block Claude's execution until they complete. For long-running tasks like deployments, test suites, or external API calls, set "async": true to run the hook in the background while Claude continues working. Async hooks cannot block or control Claude's behavior: response fields like decision, permissionDecision, and continue have no effect, because the action they would have controlled has already completed.

Configure an async hook

Add "async": true to a command hook's configuration to run it in the background without blocking Claude. This field is only available on type: "command" hooks.

This hook runs a test script after every Write tool call. Claude continues working immediately while run-tests.sh executes for up to 120 seconds. When the script finishes, its output is delivered on the next conversation turn:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/run-tests.sh",
            "async": true,
            "timeout": 120
          }
        ]
      }
    ]
  }
}

The timeout field sets the maximum time in seconds for the background process. If not specified, async hooks use the same 10-minute default as sync hooks.

How async hooks execute

When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.

After the background process exits, if the hook produced a JSON response with a systemMessage or additionalContext field, that content is delivered to Claude as context on the next conversation turn.

Async hook completion notifications are suppressed by default. To see them, enable verbose mode with Ctrl+O or start Claude Code with --verbose.

Example: run tests after file changes

This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to .claude/hooks/run-tests-async.sh in your project and make it executable with chmod +x:

#!/bin/bash
# run-tests-async.sh

# Read hook input from stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Only run tests for source files
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
  exit 0
fi

# Run tests and report results via systemMessage
RESULT=$(npm test 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
  echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
else
  echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
fi

Then add this configuration to .claude/settings.json in your project root. The async: true flag lets Claude keep working while tests run:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
            "async": true,
            "timeout": 300
          }
        ]
      }
    ]
  }
}

Limitations

Async hooks have several constraints compared to synchronous hooks:

  • Only type: "command" hooks support async. Prompt-based hooks cannot run asynchronously.
  • Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
  • Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction. Exception: an asyncRewake hook that exits with code 2 wakes Claude immediately even when the session is idle.
  • Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.

Security considerations

Disclaimer

Command hooks run with your system user's full permissions.

Warning

Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.

Security best practices

Keep these practices in mind when writing hooks:

  • Validate and sanitize inputs: never trust input data blindly
  • Always quote shell variables: use "$VAR" not $VAR
  • Block path traversal: check for .. in file paths
  • Use absolute paths: specify full paths for scripts, using "$CLAUDE_PROJECT_DIR" for the project root
  • Skip sensitive files: avoid .env, .git/, keys, etc.

Windows PowerShell tool

On Windows, you can run individual hooks in PowerShell by setting "shell": "powershell" on a command hook. Hooks spawn PowerShell directly, so this works regardless of whether CLAUDE_CODE_USE_POWERSHELL_TOOL is set. Claude Code auto-detects pwsh.exe (PowerShell 7+) with a fallback to powershell.exe (5.1).

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "shell": "powershell",
            "command": "Write-Host 'File written'"
          }
        ]
      }
    ]
  }
}

Debug hooks

Hook execution details, including which hooks matched, their exit codes, and full stdout and stderr, are written to the debug log file. Start Claude Code with claude --debug-file <path> to write the log to a known location, or run claude --debug and read the log at ~/.claude/debug/<session-id>.txt. The --debug flag does not print to the terminal.

[DEBUG] Executing hooks for PostToolUse:Write
[DEBUG] Found 1 hook commands to execute
[DEBUG] Executing hook command: <Your command> with timeout 600000ms
[DEBUG] Hook command completed with status 0: <Your stdout>

For more granular hook matching details, set CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose to see additional log lines such as hook matcher counts and query matching.

For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see Limitations and troubleshooting in the guide.

Tip

Looking to install plugins? See Discover and install plugins. For creating plugins, see Plugins. For distributing plugins, see Plugin marketplaces.

This reference provides complete technical specifications for the Claude Code plugin system, including component schemas, CLI commands, and development tools.

A plugin is a self-contained directory of components that extends Claude Code with custom functionality. Plugin components include skills, agents, hooks, MCP servers, and LSP servers.

Plugin components reference

Skills

Plugins add skills to Claude Code, creating /name shortcuts that you or Claude can invoke.

Location: skills/ or commands/ directory in plugin root

File format: Skills are directories with SKILL.md; commands are simple markdown files

Skill structure:

skills/
├── pdf-processor/
│   ├── SKILL.md
│   ├── reference.md (optional)
│   └── scripts/ (optional)
└── code-reviewer/
    └── SKILL.md

Integration behavior:

  • Skills and commands are automatically discovered when the plugin is installed
  • Claude can invoke them automatically based on task context
  • Skills can include supporting files alongside SKILL.md

For complete details, see Skills.

Agents

Plugins can provide specialized subagents for specific tasks that Claude can invoke automatically when appropriate.

Location: agents/ directory in plugin root

File format: Markdown files describing agent capabilities

Agent structure:

---
name: agent-name
description: What this agent specializes in and when Claude should invoke it
model: sonnet
effort: medium
maxTurns: 20
disallowedTools: Write, Edit
---

Detailed system prompt for the agent describing its role, expertise, and behavior.

Plugin agents support name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, and isolation frontmatter fields. The only valid isolation value is "worktree". For security reasons, hooks, mcpServers, and permissionMode are not supported for plugin-shipped agents.

Integration points:

  • Agents appear in the /agents interface
  • Claude can invoke agents automatically based on task context
  • Agents can be invoked manually by users
  • Plugin agents work alongside built-in Claude agents

For complete details, see Subagents.

Hooks

Plugins can provide event handlers that respond to Claude Code events automatically.

Location: hooks/hooks.json in plugin root, or inline in plugin.json

Format: JSON configuration with event matchers and actions

Hook configuration:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format-code.sh"
          }
        ]
      }
    ]
  }
}

Plugin hooks respond to the same lifecycle events as user-defined hooks:

Event When it fires
SessionStart When a session begins or resumes
UserPromptSubmit When you submit a prompt, before Claude processes it
PreToolUse Before a tool call executes. Can block it
PermissionRequest When a permission dialog appears
PermissionDenied When a tool call is denied by the auto mode classifier. Return {retry: true} to tell the model it may retry the denied tool call
PostToolUse After a tool call succeeds
PostToolUseFailure After a tool call fails
Notification When Claude Code sends a notification
SubagentStart When a subagent is spawned
SubagentStop When a subagent finishes
TaskCreated When a task is being created via TaskCreate
TaskCompleted When a task is being marked as completed
Stop When Claude finishes responding
StopFailure When the turn ends due to an API error. Output and exit code are ignored
TeammateIdle When an agent team teammate is about to go idle
InstructionsLoaded When a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded during a session
ConfigChange When a configuration file changes during a session
CwdChanged When the working directory changes, for example when Claude executes a cd command. Useful for reactive environment management with tools like direnv
FileChanged When a watched file changes on disk. The matcher field specifies which filenames to watch
WorktreeCreate When a worktree is being created via --worktree or isolation: "worktree". Replaces default git behavior
WorktreeRemove When a worktree is being removed, either at session exit or when a subagent finishes
PreCompact Before context compaction
PostCompact After context compaction completes
Elicitation When an MCP server requests user input during a tool call
ElicitationResult After a user responds to an MCP elicitation, before the response is sent back to the server
SessionEnd When a session terminates

Hook types:

  • command: execute shell commands or scripts
  • http: send the event JSON as a POST request to a URL
  • prompt: evaluate a prompt with an LLM (uses $ARGUMENTS placeholder for context)
  • agent: run an agentic verifier with tools for complex verification tasks

MCP servers

Plugins can bundle Model Context Protocol (MCP) servers to connect Claude Code with external tools and services.

Location: .mcp.json in plugin root, or inline in plugin.json

Format: Standard MCP server configuration

MCP server configuration:

{
  "mcpServers": {
    "plugin-database": {
      "command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
      "args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"],
      "env": {
        "DB_PATH": "${CLAUDE_PLUGIN_ROOT}/data"
      }
    },
    "plugin-api-client": {
      "command": "npx",
      "args": ["@company/mcp-server", "--plugin-mode"],
      "cwd": "${CLAUDE_PLUGIN_ROOT}"
    }
  }
}

Integration behavior:

  • Plugin MCP servers start automatically when the plugin is enabled
  • Servers appear as standard MCP tools in Claude's toolkit
  • Server capabilities integrate seamlessly with Claude's existing tools
  • Plugin servers can be configured independently of user MCP servers

LSP servers

Tip

Looking to use LSP plugins? Install them from the official marketplace: search for "lsp" in the /plugin Discover tab. This section documents how to create LSP plugins for languages not covered by the official marketplace.

Plugins can provide Language Server Protocol (LSP) servers to give Claude real-time code intelligence while working on your codebase.

LSP integration provides:

  • Instant diagnostics: Claude sees errors and warnings immediately after each edit
  • Code navigation: go to definition, find references, and hover information
  • Language awareness: type information and documentation for code symbols

Location: .lsp.json in plugin root, or inline in plugin.json

Format: JSON configuration mapping language server names to their configurations

.lsp.json file format:

{
  "go": {
    "command": "gopls",
    "args": ["serve"],
    "extensionToLanguage": {
      ".go": "go"
    }
  }
}

Inline in plugin.json:

{
  "name": "my-plugin",
  "lspServers": {
    "go": {
      "command": "gopls",
      "args": ["serve"],
      "extensionToLanguage": {
        ".go": "go"
      }
    }
  }
}

Required fields:

Field Description
command The LSP binary to execute (must be in PATH)
extensionToLanguage Maps file extensions to language identifiers

Optional fields:

Field Description
args Command-line arguments for the LSP server
transport Communication transport: stdio (default) or socket
env Environment variables to set when starting the server
initializationOptions Options passed to the server during initialization
settings Settings passed via workspace/didChangeConfiguration
workspaceFolder Workspace folder path for the server
startupTimeout Max time to wait for server startup (milliseconds)
shutdownTimeout Max time to wait for graceful shutdown (milliseconds)
restartOnCrash Whether to automatically restart the server if it crashes
maxRestarts Maximum number of restart attempts before giving up

Warning

You must install the language server binary separately. LSP plugins configure how Claude Code connects to a language server, but they don't include the server itself. If you see Executable not found in $PATH in the /plugin Errors tab, install the required binary for your language.

Available LSP plugins:

Plugin Language server Install command
pyright-lsp Pyright (Python) pip install pyright or npm install -g pyright
typescript-lsp TypeScript Language Server npm install -g typescript-language-server typescript
rust-lsp rust-analyzer See rust-analyzer installation

Install the language server first, then install the plugin from the marketplace.


Plugin installation scopes

When you install a plugin, you choose a scope that determines where the plugin is available and who else can use it:

Scope Settings file Use case
user ~/.claude/settings.json Personal plugins available across all projects (default)
project .claude/settings.json Team plugins shared via version control
local .claude/settings.local.json Project-specific plugins, gitignored
managed Managed settings Managed plugins (read-only, update only)

Plugins use the same scope system as other Claude Code configurations. For installation instructions and scope flags, see Install plugins. For a complete explanation of scopes, see Configuration scopes.


Plugin manifest schema

The .claude-plugin/plugin.json file defines your plugin's metadata and configuration. This section documents all supported fields and options.

The manifest is optional. If omitted, Claude Code auto-discovers components in default locations and derives the plugin name from the directory name. Use a manifest when you need to provide metadata or custom component paths.

Complete schema

{
  "name": "plugin-name",
  "version": "1.2.0",
  "description": "Brief plugin description",
  "author": {
    "name": "Author Name",
    "email": "[email protected]",
    "url": "https://github.com/author"
  },
  "homepage": "https://docs.example.com/plugin",
  "repository": "https://github.com/author/plugin",
  "license": "MIT",
  "keywords": ["keyword1", "keyword2"],
  "skills": "./custom/skills/",
  "commands": ["./custom/commands/special.md"],
  "agents": "./custom/agents/",
  "hooks": "./config/hooks.json",
  "mcpServers": "./mcp-config.json",
  "outputStyles": "./styles/",
  "lspServers": "./.lsp.json",
  "monitors": "./monitors.json"
}

Required fields

If you include a manifest, name is the only required field.

Field Type Description Example
name string Unique identifier (kebab-case, no spaces) "deployment-tools"

This name is used for namespacing components. For example, in the UI, the agent agent-creator for the plugin with name plugin-dev will appear as plugin-dev:agent-creator.

Metadata fields

Field Type Description Example
version string Semantic version. If also set in the marketplace entry, plugin.json takes priority. You only need to set it in one place. "2.1.0"
description string Brief explanation of plugin purpose "Deployment automation tools"
author object Author information {"name": "Dev Team", "email": "[email protected]"}
homepage string Documentation URL "https://docs.example.com"
repository string Source code URL "https://github.com/user/plugin"
license string License identifier "MIT", "Apache-2.0"
keywords array Discovery tags ["deployment", "ci-cd"]

Component path fields

Field Type Description Example
skills string|array Custom skill directories containing <name>/SKILL.md (replaces default skills/) "./custom/skills/"
commands string|array Custom flat .md skill files or directories (replaces default commands/) "./custom/cmd.md" or ["./cmd1.md"]
agents string|array Custom agent files (replaces default agents/) "./custom/agents/reviewer.md"
hooks string|array|object Hook config paths or inline config "./my-extra-hooks.json"
mcpServers string|array|object MCP config paths or inline config "./my-extra-mcp-config.json"
outputStyles string|array Custom output style files/directories (replaces default output-styles/) "./styles/"
lspServers string|array|object Language Server Protocol configs for code intelligence (go to definition, find references, etc.) "./.lsp.json"
monitors string|array|object Background Monitor configurations that auto-arm when the plugin is enabled at session start or when a skill in this plugin is invoked "./monitors.json"
userConfig object User-configurable values prompted at enable time. See User configuration See below
channels array Channel declarations for message injection (Telegram, Slack, Discord style). See Channels See below

User configuration

The userConfig field declares values that Claude Code prompts the user for when the plugin is enabled. Use this instead of requiring users to hand-edit settings.json.

{
  "userConfig": {
    "api_endpoint": {
      "description": "Your team's API endpoint",
      "sensitive": false
    },
    "api_token": {
      "description": "API authentication token",
      "sensitive": true
    }
  }
}

Keys must be valid identifiers. Each value is available for substitution as ${user_config.KEY} in MCP and LSP server configs, hook commands, and (for non-sensitive values only) skill and agent content. Values are also exported to plugin subprocesses as CLAUDE_PLUGIN_OPTION_<KEY> environment variables.

Non-sensitive values are stored in settings.json under pluginConfigs[<plugin-id>].options. Sensitive values go to the system keychain (or ~/.claude/.credentials.json where the keychain is unavailable). Keychain storage is shared with OAuth tokens and has an approximately 2 KB total limit, so keep sensitive values small.

Channels

The channels field lets a plugin declare one or more message channels that inject content into the conversation. Each channel binds to an MCP server that the plugin provides.

{
  "channels": [
    {
      "server": "telegram",
      "userConfig": {
        "bot_token": { "description": "Telegram bot token", "sensitive": true },
        "owner_id": { "description": "Your Telegram user ID", "sensitive": false }
      }
    }
  ]
}

The server field is required and must match a key in the plugin's mcpServers. The optional per-channel userConfig uses the same schema as the top-level field, letting the plugin prompt for bot tokens or owner IDs when the plugin is enabled.

Path behavior rules

For skills, commands, agents, outputStyles, and monitors, a custom path replaces the default. If the manifest specifies skills, the default skills/ directory is not scanned; if it specifies monitors, the default monitors/monitors.json is not loaded. Hooks, MCP servers, and LSP servers have different semantics for handling multiple sources.

  • All paths must be relative to the plugin root and start with ./
  • Components from custom paths use the same naming and namespacing rules
  • Multiple paths can be specified as arrays
  • To keep the default directory and add more paths for skills, commands, agents, or output styles, include the default in your array: "skills": ["./skills/", "./extras/"]
  • When a skill path points to a directory that contains a SKILL.md directly, for example "skills": ["./"] pointing to the plugin root, the frontmatter name field in SKILL.md determines the skill's invocation name. This gives a stable name regardless of the install directory. If name is not set in the frontmatter, the directory basename is used as a fallback.

Path examples:

{
  "commands": [
    "./specialized/deploy.md",
    "./utilities/batch-process.md"
  ],
  "agents": [
    "./custom-agents/reviewer.md",
    "./custom-agents/tester.md"
  ]
}

Environment variables

Claude Code provides two variables for referencing plugin paths. Both are substituted inline anywhere they appear in skill content, agent content, hook commands, and MCP or LSP server configs. Both are also exported as environment variables to hook processes and MCP or LSP server subprocesses.

${CLAUDE_PLUGIN_ROOT}: the absolute path to your plugin's installation directory. Use this to reference scripts, binaries, and config files bundled with the plugin. This path changes when the plugin updates, so files you write here do not survive an update.

${CLAUDE_PLUGIN_DATA}: a persistent directory for plugin state that survives updates. Use this for installed dependencies such as node_modules or Python virtual environments, generated code, caches, and any other files that should persist across plugin versions. The directory is created automatically the first time this variable is referenced.

{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/process.sh"
          }
        ]
      }
    ]
  }
}

Persistent data directory

The ${CLAUDE_PLUGIN_DATA} directory resolves to ~/.claude/plugins/data/{id}/, where {id} is the plugin identifier with characters outside a-z, A-Z, 0-9, _, and - replaced by -. For a plugin installed as formatter@my-marketplace, the directory is ~/.claude/plugins/data/formatter-my-marketplace/.

A common use is installing language dependencies once and reusing them across sessions and plugin updates. Because the data directory outlives any single plugin version, a check for directory existence alone cannot detect when an update changes the plugin's dependency manifest. The recommended pattern compares the bundled manifest against a copy in the data directory and reinstalls when they differ.

This SessionStart hook installs node_modules on the first run and again whenever a plugin update includes a changed package.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\""
          }
        ]
      }
    ]
  }
}

The diff exits nonzero when the stored copy is missing or differs from the bundled one, covering both first run and dependency-changing updates. If npm install fails, the trailing rm removes the copied manifest so the next session retries.

Scripts bundled in ${CLAUDE_PLUGIN_ROOT} can then run against the persisted node_modules:

{
  "mcpServers": {
    "routines": {
      "command": "node",
      "args": ["${CLAUDE_PLUGIN_ROOT}/server.js"],
      "env": {
        "NODE_PATH": "${CLAUDE_PLUGIN_DATA}/node_modules"
      }
    }
  }
}

The data directory is deleted automatically when you uninstall the plugin from the last scope where it is installed. The /plugin interface shows the directory size and prompts before deleting. The CLI deletes by default; pass --keep-data to preserve it.


Plugin caching and file resolution

Plugins are specified in one of two ways:

  • Through claude --plugin-dir, for the duration of a session.
  • Through a marketplace, installed for future sessions.

For security and verification purposes, Claude Code copies marketplace plugins to the user's local plugin cache (~/.claude/plugins/cache) rather than using them in-place. Understanding this behavior is important when developing plugins that reference external files.

Each installed version is a separate directory in the cache. When you update or uninstall a plugin, the previous version directory is marked as orphaned and removed automatically 7 days later. The grace period lets concurrent Claude Code sessions that already loaded the old version keep running without errors.

Path traversal limitations

Installed plugins cannot reference files outside their directory. Paths that traverse outside the plugin root (such as ../shared-utils) will not work after installation because those external files are not copied to the cache.

Working with external dependencies

If your plugin needs to access files outside its directory, you can create symbolic links to external files within your plugin directory. Symlinks are preserved in the cache rather than dereferenced, and they resolve to their target at runtime. The following command creates a link from inside your plugin directory to a shared utilities location:

ln -s /path/to/shared-utils ./shared-utils

This provides flexibility while maintaining the security benefits of the caching system.


Plugin directory structure

Standard plugin layout

A complete plugin follows this structure:

enterprise-plugin/
├── .claude-plugin/           # Metadata directory (optional)
│   └── plugin.json             # plugin manifest
├── skills/                   # Skills
│   ├── code-reviewer/
│   │   └── SKILL.md
│   └── pdf-processor/
│       ├── SKILL.md
│       └── scripts/
├── commands/                 # Skills as flat .md files
│   ├── status.md
│   └── logs.md
├── agents/                   # Subagent definitions
│   ├── security-reviewer.md
│   ├── performance-tester.md
│   └── compliance-checker.md
├── output-styles/            # Output style definitions
│   └── terse.md
├── monitors/                 # Background monitor configurations
│   └── monitors.json
├── hooks/                    # Hook configurations
│   ├── hooks.json           # Main hook config
│   └── security-hooks.json  # Additional hooks
├── bin/                      # Plugin executables added to PATH
│   └── my-tool               # Invokable as bare command in Bash tool
├── settings.json            # Default settings for the plugin
├── .mcp.json                # MCP server definitions
├── .lsp.json                # LSP server configurations
├── scripts/                 # Hook and utility scripts
│   ├── security-scan.sh
│   ├── format-code.py
│   └── deploy.js
├── LICENSE                  # License file
└── CHANGELOG.md             # Version history

Warning

The .claude-plugin/ directory contains the plugin.json file. All other directories (commands/, agents/, skills/, output-styles/, monitors/, hooks/) must be at the plugin root, not inside .claude-plugin/.

File locations reference

Component Default Location Purpose
Manifest .claude-plugin/plugin.json Plugin metadata and configuration (optional)
Skills skills/ Skills with <name>/SKILL.md structure
Commands commands/ Skills as flat Markdown files. Use skills/ for new plugins
Agents agents/ Subagent Markdown files
Output styles output-styles/ Output style definitions
Hooks hooks/hooks.json Hook configuration
MCP servers .mcp.json MCP server definitions
LSP servers .lsp.json Language server configurations
Monitors monitors/monitors.json Background monitor configurations
Executables bin/ Executables added to the Bash tool's PATH. Files here are invokable as bare commands in any Bash tool call while the plugin is enabled
Settings settings.json Default configuration applied when the plugin is enabled. Only the agent and subagentStatusLine keys are currently supported

CLI commands reference

Claude Code provides CLI commands for non-interactive plugin management, useful for scripting and automation.

plugin install

Install a plugin from available marketplaces.

claude plugin install <plugin> [options]

Arguments:

  • <plugin>: Plugin name or plugin-name@marketplace-name for a specific marketplace

Options:

Option Description Default
-s, --scope <scope> Installation scope: user, project, or local user
-h, --help Display help for command

Scope determines which settings file the installed plugin is added to. For example, --scope project writes to enabledPlugins in .claude/settings.json, making the plugin available to everyone who clones the project repository.

Examples:

# Install to user scope (default)
claude plugin install formatter@my-marketplace

# Install to project scope (shared with team)
claude plugin install formatter@my-marketplace --scope project

# Install to local scope (gitignored)
claude plugin install formatter@my-marketplace --scope local

plugin uninstall

Remove an installed plugin.

claude plugin uninstall <plugin> [options]

Arguments:

  • <plugin>: Plugin name or plugin-name@marketplace-name

Options:

Option Description Default
-s, --scope <scope> Uninstall from scope: user, project, or local user
--keep-data Preserve the plugin's persistent data directory
-h, --help Display help for command

Aliases: remove, rm

By default, uninstalling from the last remaining scope also deletes the plugin's ${CLAUDE_PLUGIN_DATA} directory. Use --keep-data to preserve it, for example when reinstalling after testing a new version.

plugin enable

Enable a disabled plugin.

claude plugin enable <plugin> [options]

Arguments:

  • <plugin>: Plugin name or plugin-name@marketplace-name

Options:

Option Description Default
-s, --scope <scope> Scope to enable: user, project, or local user
-h, --help Display help for command

plugin disable

Disable a plugin without uninstalling it.

claude plugin disable <plugin> [options]

Arguments:

  • <plugin>: Plugin name or plugin-name@marketplace-name

Options:

Option Description Default
-s, --scope <scope> Scope to disable: user, project, or local user
-h, --help Display help for command

plugin update

Update a plugin to the latest version.

claude plugin update <plugin> [options]

Arguments:

  • <plugin>: Plugin name or plugin-name@marketplace-name

Options:

Option Description Default
-s, --scope <scope> Scope to update: user, project, local, or managed user
-h, --help Display help for command

Debugging and development tools

Debugging commands

Use claude --debug to see plugin loading details:

This shows:

  • Which plugins are being loaded
  • Any errors in plugin manifests
  • Skill, agent, and hook registration
  • MCP server initialization

Common issues

Issue Cause Solution
Plugin not loading Invalid plugin.json Run claude plugin validate or /plugin validate to check plugin.json, skill/agent/command frontmatter, and hooks/hooks.json for syntax and schema errors
Skills not appearing Wrong directory structure Ensure skills/ or commands/ is at the plugin root, not inside .claude-plugin/
Hooks not firing Script not executable Run chmod +x script.sh
MCP server fails Missing ${CLAUDE_PLUGIN_ROOT} Use variable for all plugin paths
Path errors Absolute paths used All paths must be relative and start with ./
LSP Executable not found in $PATH Language server not installed Install the binary (e.g., npm install -g typescript-language-server typescript)

Example error messages

Manifest validation errors:

  • Invalid JSON syntax: Unexpected token } in JSON at position 142: check for missing commas, extra commas, or unquoted strings
  • Plugin has an invalid manifest file at .claude-plugin/plugin.json. Validation errors: name: Required: a required field is missing
  • Plugin has a corrupt manifest file at .claude-plugin/plugin.json. JSON parse error: ...: JSON syntax error

Plugin loading errors:

  • Warning: No commands found in plugin my-plugin custom directory: ./cmds. Expected .md files or SKILL.md in subdirectories.: command path exists but contains no valid command files
  • Plugin directory not found at path: ./plugins/my-plugin. Check that the marketplace entry has the correct path.: the source path in marketplace.json points to a non-existent directory
  • Plugin my-plugin has conflicting manifests: both plugin.json and marketplace entry specify components.: remove duplicate component definitions or remove strict: false in marketplace entry

Hook troubleshooting

Hook script not executing:

  1. Check the script is executable: chmod +x ./scripts/your-script.sh
  2. Verify the shebang line: First line should be #!/bin/bash or #!/usr/bin/env bash
  3. Check the path uses ${CLAUDE_PLUGIN_ROOT}: "command": "${CLAUDE_PLUGIN_ROOT}/scripts/your-script.sh"
  4. Test the script manually: ./scripts/your-script.sh

Hook not triggering on expected events:

  1. Verify the event name is correct (case-sensitive): PostToolUse, not postToolUse
  2. Check the matcher pattern matches your tools: "matcher": "Write|Edit" for file operations
  3. Confirm the hook type is valid: command, http, prompt, or agent

MCP server troubleshooting

Server not starting:

  1. Check the command exists and is executable
  2. Verify all paths use ${CLAUDE_PLUGIN_ROOT} variable
  3. Check the MCP server logs: claude --debug shows initialization errors
  4. Test the server manually outside of Claude Code

Server tools not appearing:

  1. Ensure the server is properly configured in .mcp.json or plugin.json
  2. Verify the server implements the MCP protocol correctly
  3. Check for connection timeouts in debug output

Directory structure mistakes

Symptoms: Plugin loads but components (skills, agents, hooks) are missing.

Correct structure: Components must be at the plugin root, not inside .claude-plugin/. Only plugin.json belongs in .claude-plugin/.

my-plugin/
├── .claude-plugin/
│   └── plugin.json      ← Only manifest here
├── commands/            ← At root level
├── agents/              ← At root level
└── hooks/               ← At root level

If your components are inside .claude-plugin/, move them to the plugin root.

Debug checklist:

  1. Run claude --debug and look for "loading plugin" messages
  2. Check that each component directory is listed in the debug output
  3. Verify file permissions allow reading the plugin files

Distribution and versioning reference

Version management

Follow semantic versioning for plugin releases:

{
  "name": "my-plugin",
  "version": "2.1.0"
}

Version format: MAJOR.MINOR.PATCH

  • MAJOR: Breaking changes (incompatible API changes)
  • MINOR: New features (backward-compatible additions)
  • PATCH: Bug fixes (backward-compatible fixes)

Best practices:

  • Start at 1.0.0 for your first stable release
  • Update the version in plugin.json before distributing changes
  • Document changes in a CHANGELOG.md file
  • Use pre-release versions like 2.0.0-beta.1 for testing

Warning

Claude Code uses the version to determine whether to update your plugin. If you change your plugin's code but don't bump the version in plugin.json, your plugin's existing users won't see your changes due to caching.

If your plugin is within a marketplace directory, you can manage the version through marketplace.json instead and omit the version field from plugin.json.


See also

  • Plugins - Tutorials and practical usage
  • Plugin marketplaces - Creating and managing marketplaces
  • Skills - Skill development details
  • Subagents - Agent configuration and capabilities
  • Hooks - Event handling and automation
  • MCP - External tool integration
  • Settings - Configuration options for plugins

Note

Channels are in research preview and require Claude Code v2.1.80 or later. They require claude.ai login. Console and API key authentication is not supported. Team and Enterprise organizations must explicitly enable them.

A channel is an MCP server that pushes events into a Claude Code session so Claude can react to things happening outside the terminal.

You can build a one-way or two-way channel. One-way channels forward alerts, webhooks, or monitoring events for Claude to act on. Two-way channels like chat bridges also expose a reply tool so Claude can send messages back. A channel with a trusted sender path can also opt in to relay permission prompts so you can approve or deny tool use remotely.

This page covers:

  • Overview: how channels work
  • What you need: requirements and general steps
  • Example: build a webhook receiver: a minimal one-way walkthrough
  • Server options: the constructor fields
  • Notification format: the event payload
  • Expose a reply tool: let Claude send messages back
  • Gate inbound messages: sender checks to prevent prompt injection
  • Relay permission prompts: forward tool approval prompts to remote channels

To use an existing channel instead of building one, see Channels. Telegram, Discord, iMessage, and fakechat are included in the research preview.

Overview

A channel is an MCP server that runs on the same machine as Claude Code. Claude Code spawns it as a subprocess and communicates over stdio. Your channel server is the bridge between external systems and the Claude Code session:

  • Chat platforms (Telegram, Discord): your plugin runs locally and polls the platform's API for new messages. When someone DMs your bot, the plugin receives the message and forwards it to Claude. No URL to expose.
  • Webhooks (CI, monitoring): your server listens on a local HTTP port. External systems POST to that port, and your server pushes the payload to Claude.
Architecture diagram showing external systems connecting to your local channel server, which communicates with Claude Code over stdio

What you need

The only hard requirement is the @modelcontextprotocol/sdk package and a Node.js-compatible runtime. Bun, Node, and Deno all work. The pre-built plugins in the research preview use Bun, but your channel doesn't have to.

Your server needs to:

  1. Declare the claude/channel capability so Claude Code registers a notification listener
  2. Emit notifications/claude/channel events when something happens
  3. Connect over stdio transport (Claude Code spawns your server as a subprocess)

The Server options and Notification format sections cover each of these in detail. See Example: build a webhook receiver for a full walkthrough.

During the research preview, custom channels aren't on the approved allowlist. Use --dangerously-load-development-channels to test locally. See Test during the research preview for details.

Example: build a webhook receiver

This walkthrough builds a single-file server that listens for HTTP requests and forwards them into your Claude Code session. By the end, anything that can send an HTTP POST, like a CI pipeline, a monitoring alert, or a curl command, can push events to Claude.

This example uses Bun as the runtime for its built-in HTTP server and TypeScript support. You can use Node or Deno instead; the only requirement is the MCP SDK.

Create the project

Create a new directory and install the MCP SDK:

mkdir webhook-channel && cd webhook-channel
bun add @modelcontextprotocol/sdk
Write the channel server

Create a file called webhook.ts. This is your entire channel server: it connects to Claude Code over stdio, and it listens for HTTP POSTs on port 8788. When a request arrives, it pushes the body to Claude as a channel event.

#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

// Create the MCP server and declare it as a channel
const mcp = new Server(
  { name: 'webhook', version: '0.0.1' },
  {
    // this key is what makes it a channel — Claude Code registers a listener for it
    capabilities: { experimental: { 'claude/channel': {} } },
    // added to Claude's system prompt so it knows how to handle these events
    instructions: 'Events from the webhook channel arrive as <channel source="webhook" ...>. They are one-way: read them and act, no reply expected.',
  },
)

// Connect to Claude Code over stdio (Claude Code spawns this process)
await mcp.connect(new StdioServerTransport())

// Start an HTTP server that forwards every POST to Claude
Bun.serve({
  port: 8788,  // any open port works
  // localhost-only: nothing outside this machine can POST
  hostname: '127.0.0.1',
  async fetch(req) {
    const body = await req.text()
    await mcp.notification({
      method: 'notifications/claude/channel',
      params: {
        content: body,  // becomes the body of the <channel> tag
        // each key becomes a tag attribute, e.g. <channel path="/" method="POST">
        meta: { path: new URL(req.url).pathname, method: req.method },
      },
    })
    return new Response('ok')
  },
})

The file does three things in order:

  • Server configuration: creates the MCP server with claude/channel in its capabilities, which is what tells Claude Code this is a channel. The instructions string goes into Claude's system prompt: tell Claude what events to expect, whether to reply, and how to route replies if it should.
  • Stdio connection: connects to Claude Code over stdin/stdout. This is standard for any MCP server: Claude Code spawns it as a subprocess.
  • HTTP listener: starts a local web server on port 8788. Every POST body gets forwarded to Claude as a channel event via mcp.notification. The content becomes the event body, and each meta entry becomes an attribute on the <channel> tag. The listener needs access to the mcp instance, so it runs in the same process. You could split it into separate modules for a larger project.
Register your server with Claude Code

Add the server to your MCP config so Claude Code knows how to start it. For a project-level .mcp.json in the same directory, use a relative path. For user-level config in ~/.claude.json, use the full absolute path so the server can be found from any project:

{
  "mcpServers": {
    "webhook": { "command": "bun", "args": ["./webhook.ts"] }
  }
}

Claude Code reads your MCP config at startup and spawns each server as a subprocess.

Test it

During the research preview, custom channels aren't on the allowlist, so start Claude Code with the development flag:

claude --dangerously-load-development-channels server:webhook

When Claude Code starts, it reads your MCP config, spawns your webhook.ts as a subprocess, and the HTTP listener starts automatically on the port you configured (8788 in this example). You don't need to run the server yourself.

If you see "blocked by org policy," your Team or Enterprise admin needs to enable channels first.

In a separate terminal, simulate a webhook by sending an HTTP POST with a message to your server. This example sends a CI failure alert to port 8788 (or whichever port you configured):

curl -X POST localhost:8788 -d "build failed on main: https://ci.example.com/run/1234"

The payload arrives in your Claude Code session as a <channel> tag:

<channel source="webhook" path="/" method="POST">build failed on main: https://ci.example.com/run/1234</channel>

In your Claude Code terminal, you'll see Claude receive the message and start responding: reading files, running commands, or whatever the message calls for. This is a one-way channel, so Claude acts in your session but doesn't send anything back through the webhook. To add replies, see Expose a reply tool.

If the event doesn't arrive, the diagnosis depends on what curl returned:

  • curl succeeds but nothing reaches Claude: run /mcp in your session to check the server's status. "Failed to connect" usually means a dependency or import error in your server file; check the debug log at ~/.claude/debug/<session-id>.txt for the stderr trace.
  • curl fails with "connection refused": the port is either not bound yet or a stale process from an earlier run is holding it. lsof -i :<port> shows what's listening; kill the stale process before restarting your session.

The fakechat server extends this pattern with a web UI, file attachments, and a reply tool for two-way chat.

Test during the research preview

During the research preview, every channel must be on the approved allowlist to register. The development flag bypasses the allowlist for specific entries after a confirmation prompt. This example shows both entry types:

# Testing a plugin you're developing
claude --dangerously-load-development-channels plugin:yourplugin@yourmarketplace

# Testing a bare .mcp.json server (no plugin wrapper yet)
claude --dangerously-load-development-channels server:webhook

The bypass is per-entry. Combining this flag with --channels doesn't extend the bypass to the --channels entries. During the research preview, the approved allowlist is Anthropic-curated, so your channel stays on the development flag while you build and test.

Note

This flag skips the allowlist only. The channelsEnabled organization policy still applies. Don't use it to run channels from untrusted sources.

Server options

A channel sets these options in the Server constructor. The instructions and capabilities.tools fields are standard MCP; capabilities.experimental['claude/channel'] and capabilities.experimental['claude/channel/permission'] are the channel-specific additions:

Field Type Description
capabilities.experimental['claude/channel'] object Required. Always {}. Presence registers the notification listener.
capabilities.experimental['claude/channel/permission'] object Optional. Always {}. Declares that this channel can receive permission relay requests. When declared, Claude Code forwards tool approval prompts to your channel so you can approve or deny them remotely. See Relay permission prompts.
capabilities.tools object Two-way only. Always {}. Standard MCP tool capability. See Expose a reply tool.
instructions string Recommended. Added to Claude's system prompt. Tell Claude what events to expect, what the <channel> tag attributes mean, whether to reply, and if so which tool to use and which attribute to pass back (like chat_id).

To create a one-way channel, omit capabilities.tools. This example shows a two-way setup with the channel capability, tools, and instructions set:

import { Server } from '@modelcontextprotocol/sdk/server/index.js'

const mcp = new Server(
  { name: 'your-channel', version: '0.0.1' },
  {
    capabilities: {
      experimental: { 'claude/channel': {} },  // registers the channel listener
      tools: {},  // omit for one-way channels
    },
    // added to Claude's system prompt so it knows how to handle your events
    instructions: 'Messages arrive as <channel source="your-channel" ...>. Reply with the reply tool.',
  },
)

To push an event, call mcp.notification with method notifications/claude/channel. The params are in the next section.

Notification format

Your server emits notifications/claude/channel with two params:

Field Type Description
content string The event body. Delivered as the body of the <channel> tag.
meta Record<string, string> Optional. Each entry becomes an attribute on the <channel> tag for routing context like chat ID, sender name, or alert severity. Keys must be identifiers: letters, digits, and underscores only. Keys containing hyphens or other characters are silently dropped.

Your server pushes events by calling mcp.notification on the Server instance. This example pushes a CI failure alert with two meta keys:

await mcp.notification({
  method: 'notifications/claude/channel',
  params: {
    content: 'build failed on main: https://ci.example.com/run/1234',
    meta: { severity: 'high', run_id: '1234' },
  },
})

The event arrives in Claude's context wrapped in a <channel> tag. The source attribute is set automatically from your server's configured name:

<channel source="your-channel" severity="high" run_id="1234">
build failed on main: https://ci.example.com/run/1234
</channel>

Expose a reply tool

If your channel is two-way, like a chat bridge rather than an alert forwarder, expose a standard MCP tool that Claude can call to send messages back. Nothing about the tool registration is channel-specific. A reply tool has three components:

  1. A tools: {} entry in your Server constructor capabilities so Claude Code discovers the tool
  2. Tool handlers that define the tool's schema and implement the send logic
  3. An instructions string in your Server constructor that tells Claude when and how to call the tool

To add these to the webhook receiver above:

Enable tool discovery

In your Server constructor in webhook.ts, add tools: {} to the capabilities so Claude Code knows your server offers tools:

capabilities: {
  experimental: { 'claude/channel': {} },
  tools: {},  // enables tool discovery
},
Register the reply tool

Add the following to webhook.ts. The import goes at the top of the file with your other imports; the two handlers go between the Server constructor and mcp.connect. This registers a reply tool that Claude can call with a chat_id and text:

// Add this import at the top of webhook.ts
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'

// Claude queries this at startup to discover what tools your server offers
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: 'Send a message back over this channel',
    // inputSchema tells Claude what arguments to pass
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: 'The conversation to reply in' },
        text: { type: 'string', description: 'The message to send' },
      },
      required: ['chat_id', 'text'],
    },
  }],
}))

// Claude calls this when it wants to invoke a tool
mcp.setRequestHandler(CallToolRequestSchema, async req => {
  if (req.params.name === 'reply') {
    const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
    // send() is your outbound: POST to your chat platform, or for local
    // testing the SSE broadcast shown in the full example below.
    send(`Reply to ${chat_id}: ${text}`)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})
Update the instructions

Update the instructions string in your Server constructor so Claude knows to route replies back through the tool. This example tells Claude to pass chat_id from the inbound tag:

instructions: 'Messages arrive as <channel source="webhook" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.'

Here's the complete webhook.ts with two-way support. Outbound replies stream over GET /events using Server-Sent Events (SSE), so curl -N localhost:8788/events can watch them live; inbound chat arrives on POST /:

#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'

// --- Outbound: write to any curl -N listeners on /events --------------------
// A real bridge would POST to your chat platform instead.
const listeners = new Set<(chunk: string) => void>()
function send(text: string) {
  const chunk = text.split('\n').map(l => `data: ${l}\n`).join('') + '\n'
  for (const emit of listeners) emit(chunk)
}

const mcp = new Server(
  { name: 'webhook', version: '0.0.1' },
  {
    capabilities: {
      experimental: { 'claude/channel': {} },
      tools: {},
    },
    instructions: 'Messages arrive as <channel source="webhook" chat_id="...">. Reply with the reply tool, passing the chat_id from the tag.',
  },
)

mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: 'Send a message back over this channel',
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: 'The conversation to reply in' },
        text: { type: 'string', description: 'The message to send' },
      },
      required: ['chat_id', 'text'],
    },
  }],
}))

mcp.setRequestHandler(CallToolRequestSchema, async req => {
  if (req.params.name === 'reply') {
    const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
    send(`Reply to ${chat_id}: ${text}`)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})

await mcp.connect(new StdioServerTransport())

let nextId = 1
Bun.serve({
  port: 8788,
  hostname: '127.0.0.1',
  idleTimeout: 0,  // don't close idle SSE streams
  async fetch(req) {
    const url = new URL(req.url)

    // GET /events: SSE stream so curl -N can watch Claude's replies live
    if (req.method === 'GET' && url.pathname === '/events') {
      const stream = new ReadableStream({
        start(ctrl) {
          ctrl.enqueue(': connected\n\n')  // so curl shows something immediately
          const emit = (chunk: string) => ctrl.enqueue(chunk)
          listeners.add(emit)
          req.signal.addEventListener('abort', () => listeners.delete(emit))
        },
      })
      return new Response(stream, {
        headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
      })
    }

    // POST: forward to Claude as a channel event
    const body = await req.text()
    const chat_id = String(nextId++)
    await mcp.notification({
      method: 'notifications/claude/channel',
      params: {
        content: body,
        meta: { chat_id, path: url.pathname, method: req.method },
      },
    })
    return new Response('ok')
  },
})

The fakechat server shows a more complete example with file attachments and message editing.

Gate inbound messages

An ungated channel is a prompt injection vector. Anyone who can reach your endpoint can put text in front of Claude. A channel listening to a chat platform or a public endpoint needs a real sender check before it emits anything.

Check the sender against an allowlist before calling mcp.notification. This example drops any message from a sender not in the set:

const allowed = new Set(loadAllowlist())  // from your access.json or equivalent

// inside your message handler, before emitting:
if (!allowed.has(message.from.id)) {  // sender, not room
  return  // drop silently
}
await mcp.notification({ ... })

Gate on the sender's identity, not the chat or room identity: message.from.id in the example, not message.chat.id. In group chats, these differ, and gating on the room would let anyone in an allowlisted group inject messages into the session.

The Telegram and Discord channels gate on a sender allowlist the same way. They bootstrap the list by pairing: the user DMs the bot, the bot replies with a pairing code, the user approves it in their Claude Code session, and their platform ID is added. See either implementation for the full pairing flow. The iMessage channel takes a different approach: it detects the user's own addresses from the Messages database at startup and lets them through automatically, with other senders added by handle.

Relay permission prompts

Note

Permission relay requires Claude Code v2.1.81 or later. Earlier versions ignore the claude/channel/permission capability.

When Claude calls a tool that needs approval, the local terminal dialog opens and the session waits. A two-way channel can opt in to receive the same prompt in parallel and relay it to you on another device. Both stay live: you can answer in the terminal or on your phone, and Claude Code applies whichever answer arrives first and closes the other.

Relay covers tool-use approvals like Bash, Write, and Edit. Project trust and MCP server consent dialogs don't relay; those only appear in the local terminal.

How relay works

When a permission prompt opens, the relay loop has four steps:

  1. Claude Code generates a short request ID and notifies your server
  2. Your server forwards the prompt and ID to your chat app
  3. The remote user replies with a yes or no and that ID
  4. Your inbound handler parses the reply into a verdict, and Claude Code applies it only if the ID matches an open request

The local terminal dialog stays open through all of this. If someone at the terminal answers before the remote verdict arrives, that answer is applied instead and the pending remote request is dropped.

Sequence diagram: Claude Code sends a permission_request notification to the channel server, the server formats and sends the prompt to the chat app, the human replies with a verdict, and the server parses that reply into a permission notification back to Claude Code

Permission request fields

The outbound notification from Claude Code is notifications/claude/channel/permission_request. Like the channel notification, the transport is standard MCP but the method and schema are Claude Code extensions. The params object has four string fields your server formats into the outgoing prompt:

Field Description
request_id Five lowercase letters drawn from a-z without l, so it never reads as a 1 or I when typed on a phone. Include it in your outgoing prompt so it can be echoed in the reply. Claude Code only accepts a verdict that carries an ID it issued. The local terminal dialog doesn't display this ID, so your outbound handler is the only way to learn it.
tool_name Name of the tool Claude wants to use, for example Bash or Write.
description Human-readable summary of what this specific tool call does, the same text the local terminal dialog shows. For a Bash call this is Claude's description of the command, or the command itself if none was given.
input_preview The tool's arguments as a JSON string, truncated to 200 characters. For Bash this is the command; for Write it's the file path and a prefix of the content. Omit it from your prompt if you only have room for a one-line message. Your server decides what to show.

The verdict your server sends back is notifications/claude/channel/permission with two fields: request_id echoing the ID above, and behavior set to 'allow' or 'deny'. Allow lets the tool call proceed; deny rejects it, the same as answering No in the local dialog. Neither verdict affects future calls.

Add relay to a chat bridge

Adding permission relay to a two-way channel takes three components:

  1. A claude/channel/permission: {} entry under experimental capabilities in your Server constructor so Claude Code knows to forward prompts
  2. A notification handler for notifications/claude/channel/permission_request that formats the prompt and sends it out through your platform API
  3. A check in your inbound message handler that recognizes yes <id> or no <id> and emits a notifications/claude/channel/permission verdict instead of forwarding the text to Claude

Only declare the capability if your channel authenticates the sender, because anyone who can reply through your channel can approve or deny tool use in your session.

To add these to a two-way chat bridge like the one assembled in Expose a reply tool:

Declare the permission capability

In your Server constructor, add claude/channel/permission: {} alongside claude/channel under experimental:

capabilities: {
  experimental: {
    'claude/channel': {},
    'claude/channel/permission': {},  // opt in to permission relay
  },
  tools: {},
},
Handle the incoming request

Register a notification handler between your Server constructor and mcp.connect. Claude Code calls it with the four request fields when a permission dialog opens. Your handler formats the prompt for your platform and includes instructions for replying with the ID:

import { z } from 'zod'

// setNotificationHandler routes by z.literal on the method field,
// so this schema is both the validator and the dispatch key
const PermissionRequestSchema = z.object({
  method: z.literal('notifications/claude/channel/permission_request'),
  params: z.object({
    request_id: z.string(),     // five lowercase letters, include verbatim in your prompt
    tool_name: z.string(),      // e.g. "Bash", "Write"
    description: z.string(),    // human-readable summary of this call
    input_preview: z.string(),  // tool args as JSON, truncated to ~200 chars
  }),
})

mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
  // send() is your outbound: POST to your chat platform, or for local
  // testing the SSE broadcast shown in the full example below.
  send(
    `Claude wants to run ${params.tool_name}: ${params.description}\n\n` +
    // the ID in the instruction is what your inbound handler parses in Step 3
    `Reply "yes ${params.request_id}" or "no ${params.request_id}"`,
  )
})
Intercept the verdict in your inbound handler

Your inbound handler is the loop or callback that receives messages from your platform: the same place you gate on sender and emit notifications/claude/channel to forward chat to Claude. Add a check before the chat-forwarding call that recognizes the verdict format and emits the permission notification instead.

The regex matches the ID format Claude Code generates: five letters, never l. The /i flag tolerates phone autocorrect capitalizing the reply; lowercase the captured ID before sending it back.

// matches "y abcde", "yes abcde", "n abcde", "no abcde"
// [a-km-z] is the ID alphabet Claude Code uses (lowercase, skips 'l')
// /i tolerates phone autocorrect; lowercase the capture before sending
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i

async function onInbound(message: PlatformMessage) {
  if (!allowed.has(message.from.id)) return  // gate on sender first

  const m = PERMISSION_REPLY_RE.exec(message.text)
  if (m) {
    // m[1] is the verdict word, m[2] is the request ID
    // emit the verdict notification back to Claude Code instead of chat
    await mcp.notification({
      method: 'notifications/claude/channel/permission',
      params: {
        request_id: m[2].toLowerCase(),  // normalize in case of autocorrect caps
        behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
      },
    })
    return  // handled as verdict, don't also forward as chat
  }

  // didn't match verdict format: fall through to the normal chat path
  await mcp.notification({
    method: 'notifications/claude/channel',
    params: { content: message.text, meta: { chat_id: String(message.chat.id) } },
  })
}

Claude Code also keeps the local terminal dialog open, so you can answer in either place, and the first answer to arrive is applied. A remote reply that doesn't exactly match the expected format fails in one of two ways, and in both cases the dialog stays open:

  • Different format: your inbound handler's regex fails to match, so text like approve it or yes without an ID falls through as a normal message to Claude.
  • Right format, wrong ID: your server emits a verdict, but Claude Code finds no open request with that ID and drops it silently.

Full example

The assembled webhook.ts below combines all three extensions from this page: the reply tool, sender gating, and permission relay. If you're starting here, you'll also need the project setup and .mcp.json entry from the initial walkthrough.

To make both directions testable from curl, the HTTP listener serves two paths:

  • GET /events: holds an SSE stream open and pushes each outbound message as a data: line, so curl -N can watch Claude's replies and permission prompts arrive live.
  • POST /: the inbound side, the same handler as earlier, now with the verdict-format check inserted before the chat-forward branch.
#!/usr/bin/env bun
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'

// --- Outbound: write to any curl -N listeners on /events --------------------
// A real bridge would POST to your chat platform instead.
const listeners = new Set<(chunk: string) => void>()
function send(text: string) {
  const chunk = text.split('\n').map(l => `data: ${l}\n`).join('') + '\n'
  for (const emit of listeners) emit(chunk)
}

// Sender allowlist. For the local walkthrough we trust the single X-Sender
// header value "dev"; a real bridge would check the platform's user ID.
const allowed = new Set(['dev'])

const mcp = new Server(
  { name: 'webhook', version: '0.0.1' },
  {
    capabilities: {
      experimental: {
        'claude/channel': {},
        'claude/channel/permission': {},  // opt in to permission relay
      },
      tools: {},
    },
    instructions:
      'Messages arrive as <channel source="webhook" chat_id="...">. ' +
      'Reply with the reply tool, passing the chat_id from the tag.',
  },
)

// --- reply tool: Claude calls this to send a message back -------------------
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: 'reply',
    description: 'Send a message back over this channel',
    inputSchema: {
      type: 'object',
      properties: {
        chat_id: { type: 'string', description: 'The conversation to reply in' },
        text: { type: 'string', description: 'The message to send' },
      },
      required: ['chat_id', 'text'],
    },
  }],
}))

mcp.setRequestHandler(CallToolRequestSchema, async req => {
  if (req.params.name === 'reply') {
    const { chat_id, text } = req.params.arguments as { chat_id: string; text: string }
    send(`Reply to ${chat_id}: ${text}`)
    return { content: [{ type: 'text', text: 'sent' }] }
  }
  throw new Error(`unknown tool: ${req.params.name}`)
})

// --- permission relay: Claude Code (not Claude) calls this when a dialog opens
const PermissionRequestSchema = z.object({
  method: z.literal('notifications/claude/channel/permission_request'),
  params: z.object({
    request_id: z.string(),
    tool_name: z.string(),
    description: z.string(),
    input_preview: z.string(),
  }),
})

mcp.setNotificationHandler(PermissionRequestSchema, async ({ params }) => {
  send(
    `Claude wants to run ${params.tool_name}: ${params.description}\n\n` +
    `Reply "yes ${params.request_id}" or "no ${params.request_id}"`,
  )
})

await mcp.connect(new StdioServerTransport())

// --- HTTP on :8788: GET /events streams outbound, POST routes inbound -------
const PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i
let nextId = 1

Bun.serve({
  port: 8788,
  hostname: '127.0.0.1',
  idleTimeout: 0,  // don't close idle SSE streams
  async fetch(req) {
    const url = new URL(req.url)

    // GET /events: SSE stream so curl -N can watch replies and prompts live
    if (req.method === 'GET' && url.pathname === '/events') {
      const stream = new ReadableStream({
        start(ctrl) {
          ctrl.enqueue(': connected\n\n')  // so curl shows something immediately
          const emit = (chunk: string) => ctrl.enqueue(chunk)
          listeners.add(emit)
          req.signal.addEventListener('abort', () => listeners.delete(emit))
        },
      })
      return new Response(stream, {
        headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
      })
    }

    // everything else is inbound: gate on sender first
    const body = await req.text()
    const sender = req.headers.get('X-Sender') ?? ''
    if (!allowed.has(sender)) return new Response('forbidden', { status: 403 })

    // check for verdict format before treating as chat
    const m = PERMISSION_REPLY_RE.exec(body)
    if (m) {
      await mcp.notification({
        method: 'notifications/claude/channel/permission',
        params: {
          request_id: m[2].toLowerCase(),
          behavior: m[1].toLowerCase().startsWith('y') ? 'allow' : 'deny',
        },
      })
      return new Response('verdict recorded')
    }

    // normal chat: forward to Claude as a channel event
    const chat_id = String(nextId++)
    await mcp.notification({
      method: 'notifications/claude/channel',
      params: { content: body, meta: { chat_id, path: url.pathname } },
    })
    return new Response('ok')
  },
})

Test the verdict path in three terminals. The first is your Claude Code session, started with the development flag so it spawns webhook.ts:

claude --dangerously-load-development-channels server:webhook

In the second, stream the outbound side so you can see Claude's replies and any permission prompts as they fire:

curl -N localhost:8788/events

In the third, send a message that will make Claude try to run a command:

curl -d "list the files in this directory" -H "X-Sender: dev" localhost:8788

The local permission dialog opens in your Claude Code terminal. A moment later the prompt appears in the /events stream, including the five-letter ID. Approve it from the remote side:

curl -d "yes <id>" -H "X-Sender: dev" localhost:8788

The local dialog closes and the tool runs. Claude's reply comes back through the reply tool and lands in the stream too.

The three channel-specific pieces in this file:

  • Capabilities in the Server constructor: claude/channel registers the notification listener, claude/channel/permission opts in to permission relay, tools lets Claude discover the reply tool.
  • Outbound paths: the reply tool handler is what Claude calls for conversational responses; the PermissionRequestSchema notification handler is what Claude Code calls when a permission dialog opens. Both call send to broadcast over /events, but they're triggered by different parts of the system.
  • HTTP handler: GET /events holds an SSE stream open so curl can watch outbound live; POST is inbound, gated on the X-Sender header. A yes <id> or no <id> body goes to Claude Code as a verdict notification and never reaches Claude; anything else is forwarded to Claude as a channel event.

Package as a plugin

To make your channel installable and shareable, wrap it in a plugin and publish it to a marketplace. Users install it with /plugin install, then enable it per session with --channels plugin:<name>@<marketplace>.

A channel published to your own marketplace still needs --dangerously-load-development-channels to run, since it isn't on the approved allowlist. To get it added, submit it to the official marketplace. Channel plugins go through security review before being approved. On Team and Enterprise plans, an admin can instead include your plugin in the organization's own allowedChannelPlugins list, which replaces the default Anthropic allowlist.

See also

  • Channels to install and use Telegram, Discord, iMessage, or the fakechat demo, and to enable channels for a Team or Enterprise org
  • Working channel implementations for complete server code with pairing flows, reply tools, and file attachments
  • MCP for the underlying protocol that channel servers implement
  • Plugins to package your channel so users can install it with /plugin install