# Claude Code hooks not working? Why they don't fire

> A Claude Code hook that never fires is either not loaded (missing from /hooks) or loaded with a matcher that matches nothing. The causes and their fixes.

Source: https://shuriken.sh/guides/claude-code-hooks-not-working

Run /hooks first: a hook it doesn't list was never loaded, usually because it sits outside the "hooks" key of a settings file, in ~/.claude.json, or under an array matcher. A listed hook that never fires usually has a matcher that matches nothing, such as a lowercase or misspelt tool name.

By Shuriken Updated 27 October 2026 6 min read

**Contents**

When Claude Code hooks are not working, type /hooks before you change anything. A hook missing from that list was never loaded, so the problem is where or how you wrote it. A hook that is listed but never fires usually has a matcher that matches no tool. Those two cases have different causes, and Anthropic's [debug guide](https://code.claude.com/docs/en/debug-your-config) lists both.

## Is the hook loaded? Check /hooks first #

/hooks opens a read-only list of every hook registered in the session, grouped by event, and labels each with where it came from: user settings, project settings, local settings or a plugin. Select one to see the command it runs and the file it lives in.

Two notices in that menu explain an empty list on their own. Only hooks from managed settings run here means your organisation set allowManagedHooksOnly, so hooks in your own files don't run and aren't listed. A hooks-disabled notice means "disableAllHooks": true is in effect, from a settings file or a --settings flag. Claude Code uses the value left after precedence, so a true in .claude/settings.local.json overrides a false in your user file.

You don't have to restart after an edit. Claude Code picks up a changed settings file after a short delay; if /hooks still shows the old version, run it again.

## Hook not showing in /hooks: the causes #

Each of these keeps a hook from loading at all.

- The hook is in a file of its own. There is no standalone hooks file for your user or project setup. Hooks go under the "hooks" key of ~/.claude/settings.json, .claude/settings.json or .claude/settings.local.json. Only plugins load a separate hooks/hooks.json.
- It is in ~/.claude.json. That file holds app state and UI toggles. hooks, permissions and env belong in ~/.claude/settings.json, a different file one folder down.
- The matcher is an array, as in "matcher": ["Edit", "Write"]. Claude Code reports it as an invalid setting when an interactive session starts and in claude doctor. Under PreToolUse or PermissionRequest it costs more: none of that file's other hooks load either. Write one string, "Edit|Write".
- The file has a // comment or a trailing comma. Settings files are strict JSON, and either one breaks the whole file: an interactive session opens with a Settings Error, and a claude -p run skips the file without a dialog. A misspelt event name such as PretoolUse costs only that entry; Claude Code shows a Settings Warning and loads the rest.
- It is a skill's frontmatter hook, and nobody has invoked the skill yet in this session. Run the skill, or ask Claude to, then open /hooks again.

For comparison, this is the shape that loads, in ~/.claude/settings.json:

json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/Users/you/.claude/hooks/check.sh" }
]
}
]
}
}

## Hook listed but not firing: the matcher #

The matcher for tool events is compared with the tool's name, and a matcher that matches nothing fails silently.

- Matching is case-sensitive. "bash" matches nothing, because tool names are capitalised: Bash, Edit, Write, Read.
- Since v2.1.191, a comma between names works: "Edit,Write" matches like "Edit|Write". In older versions a comma was taken literally and the matcher never matched. Check claude --version, and [update Claude Code](https://shuriken.sh/guides/update-claude-code) or switch to |.
- A misspelt tool name, such as "Writes", matches no tool.
- A matcher made only of letters, digits, _, -, spaces, commas and | is read as exact names, so mcp__memory matches no tool. To catch every tool of that server, write the regular expression mcp__memory__.*.

### Or it's a different event #

Some "not firing" reports are the right hook on the wrong event.

- Stop runs when Claude finishes responding, not when you press Esc. A turn that ends on an API error fires StopFailure instead.
- PostToolUse runs only after a tool succeeds; failures go to PostToolUseFailure.
- An Edit|Write hook doesn't run when a Bash command such as sed -i rewrites the file.
- PermissionRequest fires only when Claude Code is about to ask you for permission, or to deny a call that can't ask. PreToolUse fires before every tool call its matcher matches.

Then there's folder trust. In an interactive session, Claude Code holds back hooks from every settings file, your own ~/.claude/settings.json included, until you accept the workspace trust dialog for that folder.

Shuriken runs your coding agents on your own computers and keeps them going when an account hits its limit.

[Download](https://shuriken.sh/download)

## It fires, but nothing happens #

A hook that runs and fails can look like one that never ran. Press Ctrl+O for the transcript view: a failed hook shows a <hook name> hook error notice there.

- Exit code 1 doesn't block. It's a non-blocking error and the tool call goes ahead. A PreToolUse guard has to exit 2, as the blocking ones in our [eight hooks examples](https://shuriken.sh/guides/claude-code-hooks-examples) do.
- A notice ending in No such file or directory means the script never started. Point command at an absolute path or at "$CLAUDE_PROJECT_DIR"/.claude/hooks/…, run chmod +x on the script, and install jq if the notice names it.
- A slow hook times out. Command hooks get 10 minutes by default (30 seconds on UserPromptSubmit). A PreToolUse hook that times out doesn't block the call.
- JSON output can be ignored. If your shell profile prints something first, the output no longer starts with { and Claude Code reads it as plain text. A permissionDecision at the top level instead of inside hookSpecificOutput is dropped without an error.

Outside Claude Code, pipe the script the JSON a Bash call would send and print its exit code. A guard should print 2 for a call it refuses and 0 for one it lets through:

bash
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $?

## Watch it live with claude --debug #

Start a session with claude --debug, trigger the tool, and read the log at ~/.claude/debug/<session-id>.txt; it doesn't print to the terminal. claude --debug-file /tmp/claude.log writes to a path you choose, so you can tail -f it in another window, and /debug turns logging on mid-session. The log records each event, which matchers were checked, and the hook's exit code and output. Since v2.1.296, a command hook on a tool event leaves a line when it finishes, such as Hooks: PostToolUse:Write [.claude/hooks/log-write.sh] finished with status 0 (31ms).

Search it for Hook output does not start with { and Hook JSON output had unrecognized keys when the JSON is the suspect. Setting CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose adds matcher counts; our [environment variables reference](https://shuriken.sh/guides/claude-code-environment-variables) lists the others that change how Claude Code runs.

If a hook still behaves oddly, claude --safe-mode starts a session with your CLAUDE.md, skills, plugins, hooks and MCP servers turned off. When the problem goes away there, one of your customisations causes it, for example a plugin with its own hook on the same tool. When a hook's job is to block something, a deny rule may be the simpler tool: [allow, ask and deny rules](https://shuriken.sh/guides/claude-code-permission-rules) shows how they compare.

## Hooks in Shuriken #

If the hook you're fixing exists only to tell you Claude needs you, Shuriken does that without a hook: a desktop notification when a chat asks for approval or an answer, finishes or fails, for every agent and project, and never for the chat you're looking at ([how the notifications work](https://shuriken.sh/blog/desktop-notifications)).

The hooks you keep still run. An ordinary Shuriken chat in a project starts Claude Code with your user, project and local settings, the same files the terminal reads, and Shuriken adds its own hooks next to yours, not in their place.

If /hooks lists entries whose command contains hooks claude and falls back to shuriken, they come from Shuriken's Status from agents in terminals setting, which is off by default. It adds them to ~/.claude/settings.json beside your own. They do nothing outside a Shuriken terminal, and switching the setting off removes only Shuriken's entries. To get notifications without writing hooks at all, [download Shuriken](https://shuriken.sh/download).

Share

[X](https://x.com/intent/post?url=https%3A%2F%2Fshuriken.sh%2Fguides%2Fclaude-code-hooks-not-working&text=Claude%20Code%20hooks%20not%20working%3F%20Why%20they%20don't%20fire)[LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fshuriken.sh%2Fguides%2Fclaude-code-hooks-not-working)[Hacker News](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fshuriken.sh%2Fguides%2Fclaude-code-hooks-not-working&t=Claude%20Code%20hooks%20not%20working%3F%20Why%20they%20don't%20fire)[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fshuriken.sh%2Fguides%2Fclaude-code-hooks-not-working&title=Claude%20Code%20hooks%20not%20working%3F%20Why%20they%20don't%20fire)[E-mail](mailto:?subject=Claude%20Code%20hooks%20not%20working%3F%20Why%20they%20don't%20fire&body=https%3A%2F%2Fshuriken.sh%2Fguides%2Fclaude-code-hooks-not-working)

## Sources

- [Debug your configuration: Check hooks (Claude Code docs)](https://code.claude.com/docs/en/debug-your-config) (accessed 24 October 2026)
- [Hooks reference (Claude Code docs)](https://code.claude.com/docs/en/hooks) (accessed 24 October 2026)
- [Automate actions with hooks: Limitations and troubleshooting (Claude Code docs)](https://code.claude.com/docs/en/hooks-guide) (accessed 24 October 2026)
- [Settings: Fix a broken settings file (Claude Code docs)](https://code.claude.com/docs/en/settings) (accessed 24 October 2026)
- [CLI reference: --debug and --safe-mode (Claude Code docs)](https://code.claude.com/docs/en/cli-reference) (accessed 24 October 2026)
- [Claude Code changelog, v2.1.191](https://code.claude.com/docs/en/changelog) (accessed 24 October 2026)

## Frequently asked questions

**Do I need to restart Claude Code after editing a hook?**

No. Claude Code picks up an edited settings file after a short delay, even a .claude folder created after the session started (since v2.1.257). If /hooks still shows the old hook after a few seconds, run it again, and restart the session if that doesn't help.

**Why doesn't exit code 1 block the tool call?**

Exit code 1 is a non-blocking error in Claude Code: you see a hook error notice and the action goes ahead. To block a PreToolUse call, exit with code 2 and write the reason to stderr.

**Why doesn't my Stop hook run when I press Esc?**

Stop hooks don't run when you interrupt Claude. They run when Claude finishes responding, and a turn that ends on an API error fires StopFailure instead.

**Can I turn off one hook without deleting it?**

No. Anthropic's reference says there is no way to disable a single hook while keeping it in the configuration. "disableAllHooks": true turns off all of them, except hooks from managed settings.

**Does claude --safe-mode turn hooks off?**

Yes, it starts a session without your hooks, CLAUDE.md, skills, plugins and MCP servers. Hooks set in your organisation's managed settings still run.

Shuriken

The harness for your coding agents.

Run Claude Code, Codex and Gemini CLI on your own computers, move chats to your next account at a limit, and follow every chat from any device.

[Download](https://shuriken.sh/download)

[Other platforms](https://shuriken.sh/download#main)

## Keep reading

- [How to change the model in Claude Code: Guide 27 Oct 2026 3 min read Change the model in Claude Code with /model, claude --model, an environment variable or settings.json. /model wins; Enter in its picker sets your default.](https://shuriken.sh/guides/change-model-in-claude-code)
- [Claude Code API Error 400 on tool_use: what to do: Guide 27 Oct 2026 4 min read A Claude Code API Error 400 about tool use means the saved history has a tool call out of step with its result. Update, then rewind past the broken turn.](https://shuriken.sh/guides/claude-code-api-error-400-tool-use)
- [Claude Code custom slash commands: make your own: Guide 27 Oct 2026 3 min read Make your own Claude Code slash commands: one Markdown file in .claude/commands or a skill, with arguments, shell output and allowed tools. With examples.](https://shuriken.sh/guides/claude-code-custom-slash-commands)
- [Claude Code thinking and effort levels, explained: Guide 27 Oct 2026 4 min read Claude Code's effort levels, low to max, set how much Claude thinks before it acts. What each level is for, the defaults, ultrathink, and how to set it.](https://shuriken.sh/guides/claude-code-effort-levels)
- [Claude Code chat history deleted? Set cleanupPeriodDays: Guide 27 Oct 2026 5 min read Claude Code deletes session transcripts after 30 days by default. Raise cleanupPeriodDays in settings.json to keep them, and back up what you can't lose.](https://shuriken.sh/guides/claude-code-history-deleted-after-30-days)
- [Claude Code keyboard shortcuts and commands cheat sheet: Guide 27 Oct 2026 5 min read Every Claude Code keyboard shortcut worth knowing, for Mac and Windows, plus the slash commands that matter. One page, checked against Anthropic's docs.](https://shuriken.sh/guides/claude-code-keyboard-shortcuts)
- [Does Claude Code remember between sessions? How memory works: Guide 27 Oct 2026 6 min read Does Claude Code remember between sessions? Only through CLAUDE.md files and auto memory. A past chat comes back only when you resume it.](https://shuriken.sh/guides/claude-code-memory-between-sessions)

## Free tools

- [Claude limit reset tracker](https://shuriken.sh/tools/limit-resets/claude)
- [Codex limit reset tracker](https://shuriken.sh/tools/limit-resets/codex)
