When Claude Code notifications stop working, the cause is almost always one of four things: the hook is not loaded, the hook runs but fails, the event you rely on does not fire when you expect it to, or the operating system hides the alert. Run /hooks inside Claude Code to see what is loaded, start a session with claude --debug to see what each hook actually did, and then check your notification settings on the computer or phone. If you use Claude Code Notifier, ccn doctor and ccn test cover most of this in two commands.
The checklist below goes from the most common cause to the least. It works on Mac, Windows and Linux.
011. Check that Claude Code has loaded the hook
Type /hooks at the Claude Code prompt. It is a read-only browser of every hook Claude Code has loaded, and it labels each one with where it came from: user settings, project settings, local settings, a plugin or the current session. Select a hook to see the exact command and the file it is defined in.
If your hook is not in the list:
- The JSON is invalid. Settings files are strict JSON. A trailing comma or a
//comment breaks the file, and Claude Code reports a Settings Error at the next start. Runclaude doctorto see entries Claude Code rejected. - The file is in the wrong place. Personal hooks go in
~/.claude/settings.json(on Windows,%USERPROFILE%\.claude\settings.json). Project hooks go in.claude/settings.jsoninside the project. Run/statusand read theSetting sourcesline to see which files loaded. - The event name is wrong. Event names are case-sensitive:
Notification,Stop,PermissionRequest. An unknown event name shows as a Settings Warning and is skipped. - Hooks are switched off.
"disableAllHooks": truein any settings file turns every hook off. If/hookssaysOnly hooks from managed settings run here, your organisation has setallowManagedHooksOnlyand your own hooks will not run. - The edit was missed. Claude Code normally reloads settings by itself. If the hook does not appear after a few seconds, restart the session.
022. Accept workspace trust
In an interactive session, Claude Code holds back hooks from every settings file, including your own ~/.claude/settings.json, until you accept the workspace trust dialog for the folder. If you dismissed that dialog, or you are in a folder you have never opened Claude Code in, your hooks will not run there. Start claude in that folder and accept the dialog.
033. Make sure the event fires when you think it does
Many "broken" notifications are really timing. From Anthropic's hooks reference:
Notification with permission_promptA permission prompt has waited about six seconds and you have not typed. Each keystroke resets the timer.Notification with idle_promptAbout 60 seconds after Claude finishes, if you have not typed and no background agent is still running.StopEvery time Claude finishes responding. Not on an interrupt. API errors fire StopFailure instead.PermissionRequestThe moment Claude Code is about to ask you for permission.So if you test a Notification hook while typing in the terminal, it may never fire. Switch to another window and wait. For an alert the instant a turn ends, use Stop; the Stop hook guide covers it.
Also check your permission mode. Recent versions of Claude Code start interactive sessions in auto mode, where a classifier approves most actions without asking. No prompt means no permission_prompt notification and no PermissionRequest hook. To test permission alerts, press Shift+Tab until the status bar shows manual mode, then ask Claude to do something that needs approval. Permission modes explained has the details.
Matchers are case-sensitive too. A PermissionRequest matcher of bash will not match the Bash tool.
044. Run the hook command yourself
Copy the command from /hooks and run it in a terminal with sample input:
echo '{"hook_event_name":"Stop","cwd":"/tmp","last_assistant_message":"done"}' | ./my-hook.sh
echo $?
Common failures:
command not found: use an absolute path to the script, or${CLAUDE_PROJECT_DIR}for scripts in the project.- The script does nothing at all: make it executable with
chmod +x ./my-hook.sh. jq: command not found: installjq, or parse the JSON with Python or Node.js.
055. Read the debug log
Start Claude Code with claude --debug. Nothing extra prints in the terminal; the log goes to ~/.claude/debug/<session-id>.txt. To choose the path, use claude --debug-file /tmp/claude.log and run tail -f /tmp/claude.log in another terminal. If the session is already running, /debug turns logging on and tells you where the log is.
Look for lines starting Hooks:. Each one names the event, the command, how it ended (finished with status 0, timed out, failed to start) and how long it took. For more detail on matching, set CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose.
Two problems show up only here:
- Your shell profile prints something. If
~/.bashrcor~/.zshrcruns anechoand the shell sources it, that text lands in front of your hook's JSON output, and Claude Code treats the output as plain text. Search the log forHook output does not start with {. Wrap profile output inif [[ $- == *i* ]]; then ... fiso it only runs in interactive shells. - No terminal to write to. On Mac and Linux, command hooks run without a controlling terminal. A hook that tries to ring the bell or write escape codes to
/dev/ttysilently fails. Use a desktop notification command instead, or Claude Code's ownpreferredNotifChannelsetting, covered in the terminal bell guide.
066. Check the operating system
If the debug log says the hook finished with status 0 and you still saw nothing, the operating system hid it. Check that the app posting the alert is allowed to show notifications, and that Focus (Mac), Do not disturb (Windows) or your Linux desktop's do-not-disturb toggle is off. On Linux, a notification daemon must be running.
Platform-specific commands are in the guides for macOS, Windows and Linux.
07If you use Claude Code Notifier
Claude Code Notifier is an independent tool (not made by Anthropic) that installs a small program, ccn, as Claude Code hooks. Everything above still applies, because ccn is a hook like any other. Start with its own checks:
ccn doctorchecks the hooks, the credentials, the connection to the server and presence detection. Fix anything it reports first.ccn testsends a test notification. If it arrives, the problem is the event or a setting, not the pipeline.ccn deviceslists the phones paired with this computer. If none are listed, pair again withccn pair; the pairing guide walks through it.ccn config showprints the effective settings. Checknotify_on_done,notify_on_permissionandnotify_on_questionare on, and thatquiet_hoursdoes not cover the current time. Settings changed on the phone reach the computer withccn sync.
A few behaviours look like faults but are by design:
- You are at the keyboard. If you typed in the last 30 seconds, nothing waits for the phone and the terminal asks as usual. With
quiet_when_presenton (the default), the phone gets nothing while you are at the terminal Claude runs in, and an information card if you are active in another app. - Duplicate or missing alerts on a Mac. Do not run the older Mac menu bar app and
ccnon the same Mac. - A long gap. If a phone or computer is not used for 60 days, the server deletes its pairing, so you need to pair again.
On the phone itself, check that notifications are allowed for the app, and that Focus (iPhone) or Do Not Disturb (Android) is not silencing it. On Android, battery optimisation can delay notifications for some apps; if alerts arrive late, allow the app to run unrestricted in its battery settings. If you edited settings.json by hand, ccn doctor tells you whether its hooks are still in place. ccn upgrade updates to the latest release.
08FAQ
Why does my Notification hook only fire sometimes?
The permission_prompt and idle_prompt types are timed to when you appear to be away. permission_prompt waits about six seconds without typing, and idle_prompt about 60 seconds after Claude finishes. If you are typing, they are deferred. Use PermissionRequest or Stop for an immediate signal.
Where is the Claude Code debug log?
Run claude --debug and the log is written to ~/.claude/debug/<session-id>.txt. Use claude --debug-file <path> to choose the location, or /debug inside a running session.
ccn test works but real notifications do not arrive. Why?
The pipeline is fine, so look at settings and timing. Check ccn config show for notify_on_* and quiet_hours, remember that nothing is sent while you are at the terminal with quiet_when_present on, and check the permission mode in Claude Code.