OmniscioGuidesNotifications
Claude Code notifications, from the terminal bell to a hook that never fires
How Claude Code tells you it finished or is waiting, the one setting that decides which way it tells you, what each terminal does with that setting, and the documented reasons a notification hook stays silent. The hooks guide owns how to write a hook. This page owns when Claude Code notifies you at all, and what to check when it does not. For a specific error message instead, the error messages guide indexes the rest.
Checked against Anthropic's Claude Code documentation on October 26, 2026. By the Omniscio team. Updated .
Four ways Claude Code can tell you something
They are often spoken of as one feature. They are four, they fire at different moments, and only two of them come from a hook.
| Route | What it tells you | Where it is configured |
|---|---|---|
| Built-in notification | Claude needs you, or has finished and you have not typed since | preferredNotifChannel, under Local notifications in /config |
| Terminal status | Whether a session is working, waiting on you, or done, in the tab itself | The terminal, through the OSC 7501 Program Status Protocol |
Notification hook | The same moments as the built-in notification, delivered to a command you write | A Notification hook in a settings file or a plugin |
Stop hook | That the main agent has finished responding | A Stop hook in a settings file or a plugin |
Anthropic documents the hook as running alongside the built-in notification rather than replacing it. That is why a working hook and a silent desktop alert can both be true at the same time: they are separate paths that happen to share their timing.
The setting that decides how you are told
preferredNotifChannel is the whole of Claude Code's built-in notification setting, covering both the terminal bell and the desktop alert. It reads from any settings file, and notes that Claude Code also reads a value older versions left in ~/.claude.json. In /config it appears as Local notifications.
| Value | What Claude Code does |
|---|---|
auto (default) | Sends a desktop notification in iTerm2, Ghostty and Kitty; rings the bell in Terminal.app only when its audible bell is off; does nothing elsewhere |
terminal_bell | Rings the bell character in any terminal |
iterm2 | Sends an iTerm2 desktop notification |
iterm2_with_bell | Sends an iTerm2 desktop notification and rings the bell |
kitty | Sends a Kitty desktop notification |
ghostty | Sends a Ghostty desktop notification |
notifications_disabled | Sends no notification |
To turn the bell on everywhere, put one line in ~/.claude/settings.json:
{
"preferredNotifChannel": "terminal_bell"
}A mod's notifications use the same setting, so this one line covers them too.
What each terminal does with the default
A frequent reason a Claude Code setup never notifies you ends here. Under the default auto, three terminals get a desktop notification and everything else gets nothing:
- iTerm2 on macOS receives one, but it needs two settings turned on before it reaches Notification Center: Settings, Profiles, Terminal, then Notification Center Alerts, then Filter Alerts, then Send escape sequence-generated alerts.
- Ghostty and Kitty forward the notification to your operating system's notification centre with no further setup.
- Terminal.app rings the bell, and Anthropic's wording is narrow: only when Terminal's audible bell is turned off.
- Every other terminal gets nothing under
auto. Anthropic names Warp and the VS Code integrated terminal as terminals that do not receive the desktop notification, and points them atterminal_bellor a hook instead.
Two more layers sit between Claude Code and your ears.
SSH. The desktop notification reaches your local machine over SSH, so a remote session can still alert you.
tmux. Inside tmux, desktop notifications and the progress bar never reach the outer terminal by default. Adding set -g allow-passthrough on to ~/.tmux.conf and reloading it lets them through. Anthropic notes the setting has no effect on the session-status protocol below, which checks tmux itself instead.
If a desktop notification still does not appear after all of that, check that the terminal application itself is allowed to notify you in your operating system's settings. That permission lives outside Claude Code.
Session status in the tab itself
A terminal that implements the OSC 7501 Program Status Protocol can show whether each interactive session is working, waiting on you, or done. Nothing is switched on in Claude Code for this. Anthropic documents four reasons you might see no status:
- The Claude Code version is older than 2.1.295, the release Anthropic names as the minimum for status reporting.
- The session is inside tmux, where Claude Code asks tmux for support instead of your terminal.
- The session is a background session, which does not report its status to your terminal even while you are attached to it.
CLAUDE_CODE_DISABLE_TERMINAL_TITLEis set to1, which stops Claude Code checking for support or reporting status at all.
Why the alert arrives a few seconds late
A common reason a notification or a notification hook looks broken: it is being held until you appear to be away. Anthropic documents four notification types that share their timing with the desktop alerts, so in a terminal session they only fire once you stop touching the keyboard.
| Type | When it fires |
|---|---|
permission_prompt | Claude needs you to approve a tool use, and the prompt has waited about six seconds. Each keystroke defers it. |
idle_prompt | Claude finished responding about 60 seconds ago, you have not typed since, and no background agent is still running. |
elicitation_dialog | An MCP server opens an elicitation form and you have not typed for about six seconds. |
elicitation_url_dialog | An MCP server asks you to open a browser URL and you have not typed for about six seconds. |
Two consequences follow. A notification you never receive while sitting at the keyboard watching the screen is the gate working correctly. And an alert you want the moment a decision is needed is not this family at all: Anthropic points that case at the PermissionRequest hook, which the hooks guide covers.
When Claude Desktop or the VS Code extension hosts Claude Code, the permission timing is different: permission_prompt fires about six seconds after Claude asks, and typing does not defer it. Before v2.1.233 it did not fire in those hosts at all. Two things stop it there even now: a PermissionRequest hook that answers the prompt first, and CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS set to 1.
You asked to be told when the task completes
Completion is the one thing the notification types above are not about. The event for it is Stop, and it is not a notification event: it runs when the main Claude Code agent has finished responding.
Three edges are worth knowing before you build a finish chime, a status light, or a message to a chat service on it.
- An interrupt is not a completion.
Stopdoes not run if the turn ended because you interrupted it. - An API error is a different event.
StopFailureruns instead ofStop, and it is the place to log failures or alert on rate limits and authentication problems. - "Responding" is not "the whole job is done". A session can end a turn while background work or a scheduled wake is still in flight. The
Stopinput carriesbackground_tasksandsession_cronsso a hook can tell a finished session from one that is only paused waiting to be woken back up, andlast_assistant_messageholds the final text so a hook does not have to parse the transcript.
The wiring itself, the full input shape and the JSON a Stop hook can return are in the hooks guide. What matters here is the mapping: Stop for "finished", the notification types for "needs you".
Why a notification hook never fires
Work down this list in order. Each entry is something Anthropic documents, not a guess, and each one produces complete silence rather than an error.
- You are still touching the keyboard. As above, the permission, idle and elicitation types wait for you to look away. A hook that "never fires" while you sit watching the session is the gate doing its job.
- The matcher does not match the type. A
Notificationhook matches on the notification type, and Anthropic states that matchers are case sensitive. Omit the matcher to run on every type. - The handler type is not one this event accepts. Anthropic lists
Notificationamong the events that acceptcommand,httpandmcp_toolhooks but notpromptoragenthooks, so a prompt or agent handler configured there does not run. - The folder has not been trusted. 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. Accept it and the hook starts running. - Hooks are switched off, or only managed hooks are allowed.
disableAllHooksturns hooks off, andallowManagedHooksOnlylimits execution to hooks deployed by an organisation. The/hooksmenu saysOnly hooks from managed settings run herein the second case. - The hook is not where you think it is. Run
/hooksand check that the hook is listed underNotification, with the settings level it came from. If it is not listed, the file is not being read: check the location, then check that the JSON parses, because trailing commas and comments are not allowed. - The command itself fails, and the failure is invisible. For
Notificationhooks the exit code and stderr are ignored. A script with a bad path, a missingjq, or a macOS notification permission that was never granted leaves no notice anywhere on screen. That is different from a failedPreToolUsehook, which Anthropic documents as showing aPreToolUse hook errornotice in the transcript. - It returned a terminal sequence Claude Code cannot emit. A hook that raises its own desktop notification does it through the
terminalSequencefield, and Anthropic documents two limits: Claude Code writes the sequence only in an interactive session and only while its interface is on screen, so the field is ignored in a-prun and in the Agent SDK; and anything outside the OSC0,1,2,9,99,777and bare-BEL allowlist is rejected and the whole field is dropped. - You are waiting on
Stopfor somethingStopdoes not cover. An interrupt and an API error are the two documented cases where your finish hook does not run.
One more cause is outside Claude Code entirely: the terminal application's own notification permission. Anthropic's macOS example routes through Script Editor, which fails silently when it has not been granted notification permission. The hooks guide carries the fix for that.
What later releases fixed
Several notification-hook bugs were fixed in Claude Code releases rather than by a configuration change. These are the entries Anthropic's changelog records, with the release each shipped in.
| Release | What changed |
|---|---|
| 2.1.198 | Background sessions that need input or finish began firing the Notification hook as agent_needs_input and agent_completed |
| 2.1.233 | Fixed Notification hooks not firing for permission prompts when running under Claude Desktop or VS Code |
| 2.1.246 | Fixed the Notification hook not firing while the sandbox network-request prompt was waiting |
| 2.1.288 | Fixed idle_prompt notification hooks firing while background agents were still running |
Anthropic's changelog lists 2.1.310 as the current release on October 23, 2026. If your version is older than an entry above, claude --version tells you, and updating is the first thing to try.
How to find which part is broken
- Run
claude --versionand compare it with the table above. - Run
/hooksin the session and confirm the hook is listed underNotification, orStop, and that the settings level shown is the file you edited. - Run the hook's command by hand, outside Claude Code, with the documented input on stdin. Anthropic's own troubleshooting example does exactly this:
That example is aecho '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.shPreToolUsepayload; aNotificationhook's input carriesnotification_type,messageandtitleinstead. A non-zero exit code explains a silentNotificationhook, because nothing on screen will ever report it. - Start Claude Code with
claude --debugand read the log at~/.claude/debug/<session-id>.txt. Two lines are worth searching for.Hooks: <event> [<command>] completed with statusrecords that aNotificationhook ran, andHook output does not start with {explains JSON that was ignored because something printed before it. - Open the transcript with
Ctrl+Oand look for a hook error notice. OnNotificationyou will not see one, which is the point of step 3.
Which reports are still open
Every entry below was read on October 26, 2026 and carries the state it held that day. A closed report is not proof of a fix: GitHub records why a report closed, and several in this family closed as duplicates or as not planned.
Open
- #104533, filed 23 October 2026 by isamu, open: a
PreToolUsehook returningcontinue: falseends the turn, andStop,StopFailureandNotificationare all silent, so a finish notification never arrives. - #102617, filed 16 October 2026 by KamilDev, open: the
Stophook'sbackground_tasksis empty while a finished task's notification is still queued, so a hook can read a mid-chain turn end as the end of the session. - #100869, filed 9 October 2026 by kleinheinczg, open: in a plugin hooks module, the classic events including
classic.Notificationandclassic.Stopnever reach the module, while settings-file hooks for the same events fire in the same session. - #104951, filed 25 October 2026 by azim0419, open: when Claude Code refuses a request at the blocking context limit, neither
StopnorStopFailureruns.
Closed
- #85171, filed 9 August 2026 by fafacannon5, closed 5 October 2026 as not planned:
permission_promptdid not fire in v2.1.226. - #81788, filed 27 July 2026 by JBrown0x90, closed 5 September 2026 as not planned; and #66482, filed 9 June 2026 by Zeoy2020, closed 5 August 2026 as not planned, and #16114, filed 3 January 2026 by sahanxdissanayake, closed the same day as not planned: notification hooks not firing in Claude Desktop or the VS Code extension.
- #84669 by AjayMukund, #83577 by censorial and #80530 by cmsodi, all filed in July or early August 2026 and closed as duplicates on 15 August 2026: the same VS Code extension report.
- #93672, filed 11 September 2026 by BAByrne, closed 2 October 2026 as completed:
idle_promptfired while background subagents were still running, the behaviour the 2.1.288 entry above records a fix for.
"Not planned" on a notification report means Anthropic closed it without recording a fix there. The changelog entry for 2.1.233 does record one for permission prompts under Claude Desktop and VS Code. Both facts are in the record, and this page does not claim one caused the other.
The npm packages people find
Searches for this problem turn up small npm packages that promise a notification. Three of them, each described only by what its own npm page says on October 26, 2026, with the version and last publish date from that page. None was tested for this page, none is recommended, and the documented routes above need none of them.
| Package | What its own page says | Latest version | Last published |
|---|---|---|---|
| claude-code-notify | Desktop notifications for Claude Code | 0.0.5 | 2 July 2025 |
| ccnotify | CLI tool for creating Claude Code Stop Hooks with Discord, ntfy, and macOS notifications | 0.1.1 | 31 July 2025 |
| claude-notify | Transparent wrapper for Claude CLI that sends Slack notifications when Claude is waiting for input | 1.0.3 | 13 June 2025 |
The dates are the point. All three predate the Claude Code releases in the table above, so a package built against the pre-2.1.233 behaviour is not a way around a notification that does not arrive today.
When several sessions are the reason you missed one
The notification question gets harder with more than one session running, because the alert you did not hear belongs to a window you were not looking at. Omniscio is the desktop app we make for running many Claude Code sessions at once. Its Inbox is one place listing what is waiting on you across projects, and its alerts fire on the change into a state that needs you rather than on a timer. The Inbox overview describes what is collected there, the session statuses page lists all eleven states, and the notifications page covers the sounds and the rules that quiet them. The features page lists the rest, and installers are on the download page.
This is a separate layer that sits beside Claude Code's own notifications. It does not change how Claude Code notifies you, and it does not repair a hook that is not firing; the checks above are still the answer to that. If you are running several sessions and want the habits, the guide to running several sessions is where they are written down.
Sources
Every setting value, timing rule and Claude Code version note on this page comes from one of these, each read in its Markdown form on October 26, 2026. The npm columns come from each package's own registry page, and the report states come from the GitHub API, both read the same day and recorded in the fact ledger.
- Terminal configuration, for the bell and notification section, the per-terminal behaviour, the SSH note, the tmux passthrough lines, and the OSC 7501 session status rules.
- Settings reference, for every
preferredNotifChannelvalue, the Local notifications label in/config,disableAllHooksandallowManagedHooksOnly. - Hooks reference, for the
NotificationandStopevents, the notification types and their timing, the handler types each event accepts, the ignored exit code and stderr,terminalSequenceand its allowlist, workspace trust, and the debug log lines. - Hooks guide, for the "hook not firing" and "no hooks configured" checks, the stdin test, and the Script Editor permission.
- Claude Code changelog, for the 2.1.198, 2.1.233, 2.1.246 and 2.1.288 entries and for 2.1.310 as the current release.
Each of these is documented in its own section. This page reorganises the material by the question a reader actually has, and it puts the timing gate before the failure list because a hook held for six seconds and a hook that never runs look identical from the outside.
Common questions
Why does my Claude Code notification hook never fire?
Start with the timing. The permission, idle and elicitation notification types share their timing with the built-in desktop alerts, so in a terminal session they only fire once you look away: about six seconds after a prompt appears, with every keystroke restarting the timer, and about 60 seconds after Claude finishes responding. If the hook still never runs, check that it is listed by /hooks and that the matcher is the exact type name, because matchers are case sensitive. A Notification hook whose command fails leaves no notice on screen, because Claude Code ignores its exit code and stderr.
What is the difference between the Notification and Stop hooks?
Notification is the needs-you signal, split into types such as permission_prompt and idle_prompt. Stop is the turn-end signal: it runs when the main Claude Code agent has finished responding. Stop does not run if the turn ended because you interrupted it, and an API error fires StopFailure instead. A status light or a finish chime belongs on Stop; something that tells you a decision is waiting belongs on the notification types or on PermissionRequest.
Does Claude Code ring a bell or send a desktop notification by default?
Both depend on the terminal and on the preferredNotifChannel setting, which defaults to auto. Under auto, Claude Code sends a desktop notification in iTerm2, Ghostty and Kitty, rings the bell in Terminal.app only when Terminal's audible bell is turned off, and does nothing in other terminals. Set the setting to terminal_bell to ring the bell character in any terminal.
Do hooks replace the built-in notification?
No. Anthropic documents hooks as running alongside the built-in notification rather than replacing it. Turning notifications off with the notifications_disabled value changes only how you are alerted, not whether your hook runs.
Why is the notification delayed by a few seconds?
The delay is the away-from-terminal gate, not a bug. permission_prompt, idle_prompt, elicitation_dialog and elicitation_url_dialog are only sent once you appear to be away: about six seconds without typing for the permission and elicitation types, and about 60 seconds after Claude finished responding for idle_prompt, provided you have not typed since and no background agent is still running. Each keystroke defers the six-second timer.
Can I get a notification when a long Claude Code task finishes?
Use the Stop hook. It runs when the main agent finishes responding, and the input carries last_assistant_message plus background_tasks and session_crons, which let a hook tell a finished session from one that is only waiting for background work. Stop does not run on a user interrupt, and an API error fires StopFailure instead.