Your Claude Code hook is not firing: a checklist
The frustrating thing about Claude Code hooks is how quiet they are when they go wrong. A hook that never runs and a hook that runs and fails look identical from where you are sitting: nothing happens, and nothing says why.
Here is the order to check things in. It goes from the causes that are most common and cheapest to rule out, down to the ones you should only reach for once the rest are eliminated.
1. The right settings file
Hooks can live in three places, and it is easy to edit one while Claude Code reads another:
| File | Applies to |
|---|---|
~/.claude/settings.json | every session, on this machine |
.claude/settings.json | this project, shared through the repo |
.claude/settings.local.json | this project, yours only, usually gitignored |
If you are inside WSL, note that ~ means your Linux home, not C:\Users\you. A hook written into the Windows home does nothing for a session running in Ubuntu. Two homes, two files, and they are easy to confuse.
Run /hooks inside a session to see what Claude Code actually loaded. If your hook is not in that list, stop here: the rest of this page is about hooks that are loaded.
2. Restart the session
Claude Code reads its hooks when the session starts. Edit settings.json in another window and the running session keeps using the snapshot it took at launch.
This is the single most common reason a correct hook appears to do nothing. Start a fresh session before you conclude anything.
3. Valid JSON
A syntax error in settings.json does not produce a loud complaint. A trailing comma, a smart quote pasted from a web page, an unescaped backslash in a Windows path, and the file stops parsing. Your hooks go with it.
python3 -m json.tool ~/.claude/settings.json > /dev/null && echo "valid"
Backslashes deserve their own mention. In JSON, C:\Users\you\script.ps1 has to be written C:\\Users\\you\\script.ps1, and getting that wrong is the most common way to break a Windows hook config.
4. The event name, spelled exactly
Event names are case sensitive and there is no validation. Subscribe to PostToolUSE or onStop and the entry sits in your config forever, doing nothing, with no warning that the name means nothing to anyone.
Check yours against what every hook event means. The most frequent slips are Stop versus SubagentStop, and PostToolUse versus PostToolUseFailure, which are different events and fire at different moments.
5. Your command exists, from where the hook runs
Hooks do not get your interactive shell. No .bashrc, no .zshrc, no .profile. Which means:
- Aliases do not exist. If
notifyis an alias, the hook sees nothing. PATHis smaller than yours. A tool installed by a version manager, or anything you added toPATHin your shell profile, may be unreachable.- Relative paths resolve from the session's working directory, which is not necessarily where you think.
The fix is dull and reliable: absolute paths for everything.
{ "type": "command", "command": "/usr/local/bin/terminal-notifier -message 'done'" }
Confirm the same way the hook will: env -i /usr/local/bin/terminal-notifier -message test on macOS or Linux runs it with an empty environment, which is much closer to the hook's world than your terminal is.
6. Which shell got your command
On macOS and Linux, sh -c. On Windows, Git Bash if it is installed and PowerShell if it is not, and the two disagree about a surprising number of characters: @, &&, > /dev/null, $VAR. A command that parses in one is a syntax error in the other, and the failure is silent.
This is common enough on Windows to deserve its own page: which shell actually runs your hooks on Windows.
7. Read the exit code
Once you are sure the hook is loaded and the command is reachable, stop guessing and look:
claude --debug
The debug output shows each hook as it fires, with its exit code. That turns "nothing happened" into a number, and the number tells you where you are:
| Exit code | Meaning |
|---|---|
0 | Ran fine. If nothing visible happened, the problem is inside your script, not in the wiring. |
2 | Blocking. On PreToolUse this stops the tool call, on Stop it prevents Claude from stopping. |
| anything else | Non-blocking error. The action continues, and your script did not do its job. |
Exit code 2 is worth knowing about even when you are not using it deliberately. If your command can return 2 on failure, it can block a tool call by accident. Ending a fire-and-forget hook with ; exit 0 removes that risk entirely.
Nothing at all in the debug output means the hook is not loaded. Go back to step 1.
8. Which binary is running you
Last, and only if everything above checks out. There is a known issue on Windows where the native installer binary (~/.local/bin/claude.exe) does not fire user-defined hooks, while an npm-installed claude.cmd on the same machine does. Internal callbacks still appear in the logs, so it looks like hooks work in general and yours in particular is broken.
If your hooks stopped the day you changed how Claude Code was installed, check which binary is first on your PATH before you rewrite anything.
The quickest test of all
When you are deep in a config and no longer sure of anything, replace the hook with something that cannot fail and writes proof to disk:
{
"hooks": {
"Stop": [
{ "hooks": [{ "type": "command", "command": "echo fired >> /tmp/hook-check.txt" }] }
]
}
}
Restart, run a prompt, look at the file. If the line is there, the wiring is fine and your real command is the problem. If it is not, you are somewhere in steps 1 to 4, and you have saved yourself from debugging a script that was never being called.
Hooks you do not have to debug
Most people write hooks for the same reason: they walk away from a long run and want to know when it needs them. That is a lot of configuration to maintain for one question.
Blooby answers it without you writing any. It installs its hooks itself, into the platform you tell it you run Claude on, and gives every project an animated mascot that works, waits, sleeps or celebrates in step with its session. When several are running, you see which one needs you rather than reading a notification to find out.
See it for yourself
download Get Blooby free