Complete reference for Claude Code command-line interface, including commands and flags.
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 |
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 |
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.
Complete reference for commands available in Claude Code, including built-in commands and bundled skills.
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 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.
Complete reference for environment variables that control Claude Code behavior.
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.
settings.json so they apply to every sessionComplete reference for the tools Claude Code can use, including permission requirements.
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.
The Bash tool runs each command in a separate process with the following persistence behavior:
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.cd lands outside those directories, Claude Code resets to the project directory and appends Shell cwd was reset to <dir> to the tool result.CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1.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.
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:
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.
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:
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.
On Windows, Claude Code can run PowerShell commands natively instead of routing through Git Bash. This is an opt-in preview.
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.
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.
The PowerShell tool has the following known limitations during the preview:
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.
Complete reference for keyboard shortcuts, input modes, and interactive features in Claude Code sessions.
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:
"terminal.integrated.macOptionIsMeta": true in VS Code settingsSee Terminal configuration for details.
| 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 |
| 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 |
| 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 |
| 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.
| 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 |
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 |
| Shortcut | Description | Notes |
|---|---|---|
Hold Space |
Push-to-talk dictation | Requires voice dictation to be enabled. Transcript inserts at cursor. Rebindable |
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.
Enable vim-style editing via /config → Editor mode.
| 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 |
| 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.
| 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 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 |
Claude Code maintains command history for the current session:
/clear to start a new session. The previous session's conversation is preserved and can be resumed.!) is disabled by defaultPress Ctrl+R to interactively search through your command history:
Ctrl+R to activate reverse history searchCtrl+R again to cycle through older matchesTab or Esc to accept the current match and continue editingEnter to accept and execute the command immediatelyCtrl+C to cancel and restore your original inputBackspace on empty search to cancelThe search displays matching commands with the search term highlighted, so you can find and reuse previous inputs.
Claude Code supports running bash commands in the background, allowing you to continue working while long-running processes execute.
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:
Key features:
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:
! prefixRun bash commands directly without going through Claude by prefixing your input with !:
! npm test
! git status
! ls -la
Bash mode:
Ctrl+B backgrounding for long-running commands! commands in the current projectEscape, Backspace, or Ctrl+U on an empty prompt! into an empty prompt enters bash mode automatically, matching typed ! behaviorThis is useful for quick shell operations while maintaining conversation context.
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.
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
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.
/btw even while Claude is processing a response. The side question runs independently and does not interrupt the main turn.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.
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.
Ctrl+T to toggle the task list view. The display shows up to 10 tasks at a timeCLAUDE_CODE_TASK_LIST_ID to use a named directory in ~/.claude/tasks/: CLAUDE_CODE_TASK_LIST_ID=my-project claudeWhen 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:
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).
Track, rewind, and summarize Claude's edits and conversation to manage session state.
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.
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.
Claude Code tracks all changes made by its file editing tools:
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:
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.
The three restore options revert state: they undo code changes, conversation history, or both. "Summarize from here" works differently:
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).
Checkpoints are particularly useful when:
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.
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.
Checkpoints are designed for quick, session-level recovery. For permanent version history and collaboration:
/rewindReference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, HTTP hooks, prompt hooks, and MCP tool hooks.
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.
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):
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 |
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:
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" }, ... }
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.
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.
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 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.
Hooks are defined in JSON settings files. The configuration has three levels of nesting:
PreToolUse or StopSee 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.
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.
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.
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 toolmcp__filesystem__read_file: Filesystem server's read file toolmcp__github__search_repositories: GitHub server's search toolTo 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 servermcp__.*__write.* matches any tool whose name starts with write from any serverThis 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"
}
]
}
]
}
}
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:
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.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.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.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.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 |
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 |
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"]
}
]
}
]
}
}
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.
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"
}
]
}
]
}
}
Define plugin hooks in hooks/hooks.json with an optional top-level description field. When a plugin is enabled, its hooks merge with your user and project hooks.
This example runs a formatting script bundled with the plugin:
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
}
See the plugin components reference for details on creating plugin hooks.
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.
/hooks menuType /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.jsonProject: from .claude/settings.jsonLocal: from .claude/settings.local.jsonPlugin: from a plugin's hooks/hooks.jsonSession: registered in memory for the current sessionBuilt-in: registered internally by Claude CodeSelecting 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.
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.
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.
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.
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 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 hooks use HTTP status codes and response bodies instead of exit codes and stdout:
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.
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:
continue work across all events. These are listed in the table below.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" }
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"
}
Uses hookSpecificOutput for richer control: allow, deny, or escalate to the user. You can also modify tool input before it runs or inject additional context for Claude. See PreToolUse decision control for the full set of options.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}
Uses hookSpecificOutput to allow or deny a permission request on behalf of the user. When allowing, you can also modify the tool's input or apply permission rules so the user isn't prompted again. See PermissionRequest decision control for the full set of options.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
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.
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.
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 |
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"
}
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"
}
}
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.
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.
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 hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
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.
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 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:
additionalContext: use the JSON format below for more control. The additionalContext field is added as contextPlain 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.
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.
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:
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 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" 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:
AskUserQuestion. The PreToolUse hook fires.permissionDecision: "defer". The tool does not execute. The process exits with stop_reason: "tool_deferred" and the pending tool call preserved in the transcript.deferred_tool_use from the SDK result, surfaces the question in its own UI, and waits for an answer.claude -p --resume <session-id>. The same tool call fires PreToolUse again.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.
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 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 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"
}
}
}
}
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.
Runs immediately after a tool completes successfully.
Matches on tool name, same values as PreToolUse.
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 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"
}
}
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 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 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"
}
}
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.
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 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.
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"
}
]
}
]
}
}
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 |
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/).
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"
}
}
Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
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.
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.
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 hooks support two ways to control task creation:
{"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
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.
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 hooks support two ways to control task completion:
{"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
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.
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 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"
}
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.
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.
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.
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 hooks support two ways to control teammate behavior:
{"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
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"
}
]
}
]
}
}
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 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.
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.
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"
}
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.
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:
| 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.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.
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"
}
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.
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.
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 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:
type: "command"): print the path on stdout.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.
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'"
}
]
}
]
}
}
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.
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.
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": ""
}
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 |
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.
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 |
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
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.
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"
}
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.
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.
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"
}
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.
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):
PermissionRequestPostToolUsePostToolUseFailurePreToolUseStopSubagentStopTaskCompletedTaskCreatedUserPromptSubmitEvents that support command and http hooks but not prompt or agent:
ConfigChangeCwdChangedElicitationElicitationResultFileChangedInstructionsLoadedNotificationPermissionDeniedPostCompactPreCompactSessionEndStopFailureSubagentStartTeammateIdleWorktreeCreateWorktreeRemoveSessionStart supports only command hooks.
Instead of executing a Bash command, prompt-based hooks:
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 |
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 |
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 (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.
When an agent hook fires:
{ "ok": true/false } decisionAgent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
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
}
]
}
]
}
}
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.
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.
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.
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
}
]
}
]
}
}
Async hooks have several constraints compared to synchronous hooks:
type: "command" hooks support async. Prompt-based hooks cannot run asynchronously.asyncRewake hook that exits with code 2 wakes Claude immediately even when the session is idle.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.
Keep these practices in mind when writing hooks:
"$VAR" not $VAR.. in file paths"$CLAUDE_PROJECT_DIR" for the project root.env, .git/, keys, etc.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'"
}
]
}
]
}
}
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.
Complete technical reference for Claude Code plugin system, including schemas, CLI commands, and component specifications.
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.
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:
For complete details, see Skills.
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 interfaceFor complete details, see Subagents.
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 scriptshttp: send the event JSON as a POST request to a URLprompt: evaluate a prompt with an LLM (uses $ARGUMENTS placeholder for context)agent: run an agentic verifier with tools for complex verification tasksPlugins 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:
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:
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.
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.
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.
{
"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"
}
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.
| 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"] |
| 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 |
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.
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.
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.
./"skills": ["./skills/", "./extras/"]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"
]
}
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"
}
]
}
]
}
}
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.
Plugins are specified in one of two ways:
claude --plugin-dir, for the duration of a session.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.
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.
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.
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/.
| 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 |
Claude Code provides CLI commands for non-interactive plugin management, useful for scripting and automation.
Install a plugin from available marketplaces.
claude plugin install <plugin> [options]
Arguments:
<plugin>: Plugin name or plugin-name@marketplace-name for a specific marketplaceOptions:
| 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
Remove an installed plugin.
claude plugin uninstall <plugin> [options]
Arguments:
<plugin>: Plugin name or plugin-name@marketplace-nameOptions:
| 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.
Enable a disabled plugin.
claude plugin enable <plugin> [options]
Arguments:
<plugin>: Plugin name or plugin-name@marketplace-nameOptions:
| Option | Description | Default |
|---|---|---|
-s, --scope <scope> |
Scope to enable: user, project, or local |
user |
-h, --help |
Display help for command |
Disable a plugin without uninstalling it.
claude plugin disable <plugin> [options]
Arguments:
<plugin>: Plugin name or plugin-name@marketplace-nameOptions:
| Option | Description | Default |
|---|---|---|
-s, --scope <scope> |
Scope to disable: user, project, or local |
user |
-h, --help |
Display help for command |
Update a plugin to the latest version.
claude plugin update <plugin> [options]
Arguments:
<plugin>: Plugin name or plugin-name@marketplace-nameOptions:
| Option | Description | Default |
|---|---|---|
-s, --scope <scope> |
Scope to update: user, project, local, or managed |
user |
-h, --help |
Display help for command |
Use claude --debug to see plugin loading details:
This shows:
| 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) |
Manifest validation errors:
Invalid JSON syntax: Unexpected token } in JSON at position 142: check for missing commas, extra commas, or unquoted stringsPlugin has an invalid manifest file at .claude-plugin/plugin.json. Validation errors: name: Required: a required field is missingPlugin has a corrupt manifest file at .claude-plugin/plugin.json. JSON parse error: ...: JSON syntax errorPlugin 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 filesPlugin 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 directoryPlugin my-plugin has conflicting manifests: both plugin.json and marketplace entry specify components.: remove duplicate component definitions or remove strict: false in marketplace entryHook script not executing:
chmod +x ./scripts/your-script.sh#!/bin/bash or #!/usr/bin/env bash${CLAUDE_PLUGIN_ROOT}: "command": "${CLAUDE_PLUGIN_ROOT}/scripts/your-script.sh"./scripts/your-script.shHook not triggering on expected events:
PostToolUse, not postToolUse"matcher": "Write|Edit" for file operationscommand, http, prompt, or agentServer not starting:
${CLAUDE_PLUGIN_ROOT} variableclaude --debug shows initialization errorsServer tools not appearing:
.mcp.json or plugin.jsonSymptoms: 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:
claude --debug and look for "loading plugin" messagesFollow semantic versioning for plugin releases:
{
"name": "my-plugin",
"version": "2.1.0"
}
Version format: MAJOR.MINOR.PATCH
Best practices:
1.0.0 for your first stable releaseplugin.json before distributing changesCHANGELOG.md file2.0.0-beta.1 for testingWarning
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.
Build an MCP server that pushes webhooks, alerts, and chat messages into a Claude Code session. Reference for the channel contract: capability declaration, notification events, reply tools, sender gating, and permission relay.
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:
To use an existing channel instead of building one, see Channels. Telegram, Discord, iMessage, and fakechat are included in the research preview.
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:
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:
claude/channel capability so Claude Code registers a notification listenernotifications/claude/channel events when something happensThe 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.
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 a new directory and install the MCP SDK:
mkdir webhook-channel && cd webhook-channel
bun add @modelcontextprotocol/sdk
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:
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.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.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.
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.
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.
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.
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>
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:
tools: {} entry in your Server constructor capabilities so Claude Code discovers the toolinstructions string in your Server constructor that tells Claude when and how to call the toolTo add these to the webhook receiver above:
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
},
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 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.
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.
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.
When a permission prompt opens, the relay loop has four steps:
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.
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.
Adding permission relay to a two-way channel takes three components:
claude/channel/permission: {} entry under experimental capabilities in your Server constructor so Claude Code knows to forward promptsnotifications/claude/channel/permission_request that formats the prompt and sends it out through your platform APIyes <id> or no <id> and emits a notifications/claude/channel/permission verdict instead of forwarding the text to ClaudeOnly 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:
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: {},
},
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}"`,
)
})
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:
approve it or yes without an ID falls through as a normal message to Claude.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:
Server constructor: claude/channel registers the notification listener, claude/channel/permission opts in to permission relay, tools lets Claude discover the reply tool.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.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.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.
/plugin install