Omniscio

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.

RouteWhat it tells youWhere it is configured
Built-in notificationClaude needs you, or has finished and you have not typed sincepreferredNotifChannel, under Local notifications in /config
Terminal statusWhether a session is working, waiting on you, or done, in the tab itselfThe terminal, through the OSC 7501 Program Status Protocol
Notification hookThe same moments as the built-in notification, delivered to a command you writeA Notification hook in a settings file or a plugin
Stop hookThat the main agent has finished respondingA 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.

ValueWhat 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_bellRings the bell character in any terminal
iterm2Sends an iTerm2 desktop notification
iterm2_with_bellSends an iTerm2 desktop notification and rings the bell
kittySends a Kitty desktop notification
ghosttySends a Ghostty desktop notification
notifications_disabledSends 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 at terminal_bell or 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_TITLE is set to 1, 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.

TypeWhen it fires
permission_promptClaude needs you to approve a tool use, and the prompt has waited about six seconds. Each keystroke defers it.
idle_promptClaude finished responding about 60 seconds ago, you have not typed since, and no background agent is still running.
elicitation_dialogAn MCP server opens an elicitation form and you have not typed for about six seconds.
elicitation_url_dialogAn 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. Stop does not run if the turn ended because you interrupted it.
  • An API error is a different event. StopFailure runs instead of Stop, 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 Stop input carries background_tasks and session_crons so a hook can tell a finished session from one that is only paused waiting to be woken back up, and last_assistant_message holds 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.

  1. 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.
  2. The matcher does not match the type. A Notification hook matches on the notification type, and Anthropic states that matchers are case sensitive. Omit the matcher to run on every type.
  3. The handler type is not one this event accepts. Anthropic lists Notification among the events that accept command, http and mcp_tool hooks but not prompt or agent hooks, so a prompt or agent handler configured there does not run.
  4. 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.
  5. Hooks are switched off, or only managed hooks are allowed. disableAllHooks turns hooks off, and allowManagedHooksOnly limits execution to hooks deployed by an organisation. The /hooks menu says Only hooks from managed settings run here in the second case.
  6. The hook is not where you think it is. Run /hooks and check that the hook is listed under Notification, 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.
  7. The command itself fails, and the failure is invisible. For Notification hooks the exit code and stderr are ignored. A script with a bad path, a missing jq, or a macOS notification permission that was never granted leaves no notice anywhere on screen. That is different from a failed PreToolUse hook, which Anthropic documents as showing a PreToolUse hook error notice in the transcript.
  8. It returned a terminal sequence Claude Code cannot emit. A hook that raises its own desktop notification does it through the terminalSequence field, 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 -p run and in the Agent SDK; and anything outside the OSC 0, 1, 2, 9, 99, 777 and bare-BEL allowlist is rejected and the whole field is dropped.
  9. You are waiting on Stop for something Stop does 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.

ReleaseWhat changed
2.1.198Background sessions that need input or finish began firing the Notification hook as agent_needs_input and agent_completed
2.1.233Fixed Notification hooks not firing for permission prompts when running under Claude Desktop or VS Code
2.1.246Fixed the Notification hook not firing while the sandbox network-request prompt was waiting
2.1.288Fixed 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

  1. Run claude --version and compare it with the table above.
  2. Run /hooks in the session and confirm the hook is listed under Notification, or Stop, and that the settings level shown is the file you edited.
  3. Run the hook's command by hand, outside Claude Code, with the documented input on stdin. Anthropic's own troubleshooting example does exactly this:
    echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
    That example is a PreToolUse payload; a Notification hook's input carries notification_type, message and title instead. A non-zero exit code explains a silent Notification hook, because nothing on screen will ever report it.
  4. Start Claude Code with claude --debug and read the log at ~/.claude/debug/<session-id>.txt. Two lines are worth searching for. Hooks: <event> [<command>] completed with status records that a Notification hook ran, and Hook output does not start with { explains JSON that was ignored because something printed before it.
  5. Open the transcript with Ctrl+O and look for a hook error notice. On Notification you 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 PreToolUse hook returning continue: false ends the turn, and Stop, StopFailure and Notification are all silent, so a finish notification never arrives.
  • #102617, filed 16 October 2026 by KamilDev, open: the Stop hook's background_tasks is 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.Notification and classic.Stop never 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 Stop nor StopFailure runs.

Closed

  • #85171, filed 9 August 2026 by fafacannon5, closed 5 October 2026 as not planned: permission_prompt did 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_prompt fired 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.

PackageWhat its own page saysLatest versionLast published
claude-code-notifyDesktop notifications for Claude Code0.0.52 July 2025
ccnotifyCLI tool for creating Claude Code Stop Hooks with Discord, ntfy, and macOS notifications0.1.131 July 2025
claude-notifyTransparent wrapper for Claude CLI that sends Slack notifications when Claude is waiting for input1.0.313 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 preferredNotifChannel value, the Local notifications label in /config, disableAllHooks and allowManagedHooksOnly.
  • Hooks reference, for the Notification and Stop events, the notification types and their timing, the handler types each event accepts, the ignored exit code and stderr, terminalSequence and 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.