Skip to content
AI Dev Toolkit.
Esc
  • AI Token CounterCount tokens for GPT, Claude, Gemini, DeepSeek, Qwen and more.Tool
  • LLM API Cost CalculatorEstimate per-request, daily and monthly API costs.Tool
  • AI Model ComparisonCompare prices, context windows and features across models.Tool
  • AI Model Pricing PagesSpecs, real costs and cheaper alternatives for popular models.Tool
  • Context Window CheckerSee whether your text fits each model’s context window.Tool
  • Subscription vs API CalculatorFind out whether a chat plan or the API is cheaper for you.Tool
  • GPU / VRAM CalculatorCheck how much VRAM a local model needs and which GPUs fit.Tool
  • Prompt Caching CalculatorEstimate savings from prompt caching.Tool

Coding Agents

Claude Code hooks builder

Build the hooks block for Claude Code’s settings.json from tested examples, with the Bash or PowerShell script each one runs, and check hooks you already have. It’s free and runs in your browser.

Options
Scripts for
Pick hooks
  • One per line. * and ? are wildcards; matched on the file name, ignoring case.

    One per line. Any file inside a folder with this name, at any depth.

    File names that stay editable even if they match above.

Custom hooks

Save as .claude/settings.json. If the file already exists, paste it below to merge.

.claude/settings.json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh",
            "timeout": 30,
            "onFailure": "block"
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh",
            "timeout": 30,
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}
  • The Bash scripts need jq: brew install jq (macOS), sudo apt-get install jq (Debian, Ubuntu) or winget install jqlang.jq (Windows). Without it, each guard blocks instead of running unchecked.
  • Make the scripts executable on macOS and Linux: chmod +x .claude/hooks/*.sh. Git Bash on Windows runs them as they are.
  • block-rm checks the full command text, so it also catches rm inside &&, pipes, $( ) and sudo. It also blocks git rm -r; edit the pattern if you need that.
  • block-rm is a pattern check, not a sandbox. It misses other ways to delete a folder, such as find -delete, git clean, a command built from variables or a script Claude writes. For a hard rule, add permission deny rules too, such as Bash(rm -rf *).
  • protect-files only sees the Edit and Write tools, and it checks the path Claude gives, not where a symlink points. A shell command such as echo KEY=1 >> .env gets past it; a permission deny rule such as Edit(.env) also covers shell redirects like that, and only the sandbox stops every process.
  • To stop Claude reading .env as well, add a permission deny rule such as Read(./.env).
  • Run /hooks in Claude Code to check they loaded. Hooks run with your user permissions: read every script before you save it.

Everything is generated in your browser; nothing you type or paste is sent anywhere.

Check and merge your existing hooks

Paste a settings.json, a hooks object or a plugin’s hooks/hooks.json. It’s checked against the hooks reference: event names, matchers, handler fields, which events support each handler type, and common Windows and quoting mistakes.

Steps

How to use the Claude Code hooks builder

  1. Pick the hooks you want, or add a custom one, and edit any options such as the protected file names.
  2. Choose Bash or PowerShell for the scripts and which settings file the hooks go in.
  3. If the settings file already exists, paste it into the checker so the new hooks are merged in.
  4. Save each file where its tab says, run chmod +x on the Bash scripts, and run /hooks in Claude Code to confirm they loaded.

Method

How it works

A Claude Code hook is a command, HTTP call, MCP tool call, prompt or subagent that Claude Code runs itself at a fixed point in a session, such as just before a tool call or when Claude finishes responding. Instructions in CLAUDE.md are advice the model usually follows; a hook runs every time its event fires, whatever the model decides. That makes hooks the place for rules that must hold, like never editing .env, and chores that should never be forgotten, like formatting. For a walk through every event with worked examples, read the Claude Code hooks guide.

Event, matcher, handler

Every hook has three parts. The event is the moment: PreToolUse before a tool runs, PostToolUse after it succeeds, UserPromptSubmit, Stop, SessionStart and 28 more. The matcher narrows it: on tool events it’s the tool name, so Edit|Write means either tool. A matcher made only of letters, digits, _, -, spaces, commas and | is compared exactly and is case-sensitive; anything else is an unanchored JavaScript regular expression, so Edit.* also matches NotebookEdit. Ten events ignore the matcher entirely. The handler is what runs. On tool events an if field can narrow one handler further with permission-rule syntax such as Bash(git push *), but on any other event a handler with if never runs.

How a hook answers

A command hook gets the event as JSON on stdin. Exit 0 means success, and Claude Code reads any JSON object printed on stdout. Exit 2 is a blocking error: on events that can block, the action stops and stderr is the reason (on PreToolUse Claude reads it and changes course). Any other exit code, including the usual Unix failure code 1, is a non-blocking error and the action goes ahead. For finer control a PreToolUse hook prints hookSpecificOutput.permissionDecision as allow, deny or ask, while PostToolUse, Stop and others use a top-level "decision": "block".

Why the guards fail closed

The quiet danger with hooks is that failure lets things through. A mistyped script path, a missing jq, a crash or a timeout is a non-blocking error, so a guard that breaks stops guarding without anyone noticing. Each guard here checks for its tools and its input and blocks if either is missing, and the option above adds "onFailure": "block" (Claude Code 2.1.295 or later) so that a script that can’t even start blocks too. Guards are still pattern checks, not a sandbox: for hard limits, pair them with permission deny rules, which a hook’s allow can never override; the settings.json and permissions builder writes those.

How it was checked

Every event, matcher rule, handler field, default timeout and exit-code effect comes from Claude Code’s hooks reference and hooks guide, read on 2026-10-13, when the newest release was 2.1.296. Each example’s script was run, exactly as its settings entry starts it, against sample hook input in Git Bash and Windows PowerShell 5.1, covering commands that must be blocked (including quoted and abbreviated forms) and ones that must pass, Windows and Unix paths, non-English characters, missing jq, unreadable input and paths that climb out of the project. The Bash guards are also run in the site’s unit tests, and every generated settings block passes this page’s own checker. Nothing was run inside a live Claude Code session, so behaviour there is as documented.

Examples

Worked examples

Block recursive deletes and notify when Claude stops

.claude/settings.json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|PowerShell",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh",
            "timeout": 30,
            "onFailure": "block"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "printf '%s\\n' '{\"terminalSequence\":\"\\u001b]9;Claude Code needs your approval\\u0007\"}'"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "printf '%s\\n' '{\"terminalSequence\":\"\\u001b]9;Claude Code finished\\u0007\"}'"
          }
        ]
      }
    ]
  }
}

The first hook runs .claude/hooks/block-rm.sh before every shell command; the other two print a terminal notification that Claude Code shows for you.

Every hook event at a glance

Claude Code hook events: what each matches on, what exit code 2 does, and which handler types it runs
EventMatcherExit code 2Handlers
SessionStarthow the session startedShows stderr to you only; the session goes oncommand, mcp_tool
Setupthe CLI flagIgnoredcommand
UserPromptSubmitNoneBlocks the prompt; stderr is shown to you, not to Claudecommand, http, mcp_tool, prompt, agent
UserPromptExpansioncommand nameBlocks the expansion; stderr is shown to youcommand, http, mcp_tool, prompt, agent
PreToolUsetool nameBlocks the tool call; Claude reads stderr as the reasoncommand, http, mcp_tool, prompt, agent
PermissionRequesttool nameNot honoured; the permission flow goes on unchanged. Deny with the decision object insteadcommand, http, mcp_tool, prompt
PermissionDeniedtool nameIgnored; the denial already happenedcommand, http, mcp_tool, prompt, agent
PostToolUsetool nameShows stderr to Claude; the tool already rancommand, http, mcp_tool, prompt, agent
PostToolUseFailuretool nameShows stderr to Claude; the tool already failedcommand, http, mcp_tool, prompt, agent
PostToolBatchNoneStops the agentic loop before the next model callcommand, http, mcp_tool, prompt, agent
Notificationnotification typeIgnoredcommand, http, mcp_tool
MessageDisplayNoneThe original text is displayedcommand, http, mcp_tool
SubagentStartagent typeShows stderr to you onlycommand, http, mcp_tool
SubagentStopagent typeKeeps the subagent working, with stderr as its next instructioncommand, http, mcp_tool, prompt, agent
TaskCreatedNoneCancels the task; Claude gets stderr as the tool errorcommand, http, mcp_tool, prompt, agent
TaskCompletedNoneThe task stays open; stderr goes to Claudecommand, http, mcp_tool, prompt, agent
StopNoneClaude keeps working, with stderr as the reasoncommand, http, mcp_tool, prompt, agent
StopFailureerror typeIgnoredcommand, http, mcp_tool
TeammateIdleNoneThe teammate keeps working, with stderr as feedbackcommand, http, mcp_tool, prompt, agent
InstructionsLoadedload reasonIgnoredcommand, http, mcp_tool
ConfigChangeconfiguration sourceThe change doesn’t apply to the session (policy_settings always applies)command, http, mcp_tool
CwdChangedNoneShows stderr to you onlycommand, http, mcp_tool
DirectoryAddedhow it was addedStderr goes to the debug logcommand, http, mcp_tool
FileChangedfile names to watch, split on |Shows stderr to you onlycommand, http, mcp_tool
WorktreeCreateNoneAny non-zero exit fails the creationcommand, http, mcp_tool
WorktreeRemoveNoneAny non-zero exit fails the removal if the directory still existscommand, http, mcp_tool
PreCompactwhat triggered compactionBlocks compactioncommand, http, mcp_tool
PostCompactwhat triggered compactionShows stderr to you onlycommand, http, mcp_tool
PreModelSwitchmodel switched toBlocks the switch and shows stderr to youcommand, http, mcp_tool
PostModelSwitchmodel switched toShows stderr to you onlycommand, http, mcp_tool
ElicitationMCP server nameDeclines the request; no dialog appearscommand, http, mcp_tool
ElicitationResultMCP server nameBlocks the response (it becomes a decline)command, http, mcp_tool
SessionEndwhy it endedShows stderr to you onlycommand, http, mcp_tool

From the hooks reference, checked 2026-10-13. Default timeouts: 600 seconds for command, http and mcp_tool (30 on UserPromptSubmit and model switches, 10 on MessageDisplay), 30 for prompt, 60 for agent; SessionEnd hooks share a 1.5-second budget.

FAQ

Frequently asked questions

Where do Claude Code hooks go?

Under a hooks key in a settings file: ~/.claude/settings.json for all your projects, .claude/settings.json to share them with your team through the repository, or .claude/settings.local.json for yourself in one project. Hooks from every file merge rather than replace each other. Run /hooks in Claude Code to see which loaded and where each came from.

How do I block a command with a hook?

Use a PreToolUse hook with the matcher Bash|PowerShell. The script reads the command from tool_input.command on stdin; to block it, it writes a reason to stderr and exits with code 2, and Claude reads the reason. Exit code 1 doesn’t block. The block-rm example here does exactly this for recursive deletes.

What happens if my hook script fails?

By default the action goes ahead: a missing script, a crash or a timeout is a non-blocking error, so a broken guard lets everything through. From Claude Code 2.1.295, "onFailure": "block" on a command or HTTP hook makes those failures block instead. The guards built here also block on their own when jq is missing or the input can’t be read.

Do Claude Code hooks work on Windows?

Yes. Command hooks run in Git Bash when it’s installed, otherwise in PowerShell. File paths arrive with backslashes, Git Bash has no jq until you install it, and shell guards should match Bash|PowerShell, because Claude may run commands with the PowerShell tool. Pick the PowerShell option to get scripts that need neither Git Bash nor jq.

Why doesn’t my hook fire?

Check /hooks first. The usual causes are a matcher in the wrong case (bash instead of Bash), an MCP matcher without .*, an if field on an event that isn’t a tool event (the hook then never runs), or invalid JSON in the settings file. Paste your file into the checker on this page to find these.

Are hooks safe to copy from the internet?

A command hook runs with your full user permissions, every time its event fires, so read it like any script before saving it. In an interactive session Claude Code holds back settings hooks until you trust the folder, but -p and SDK runs don’t ask. The examples here never delete anything or install packages; the formatter only rewrites files inside the project.