Get notified when Claude Code needs you or finishes
Set up Claude Code notifications on macOS: the built-in setting, Notification and Stop hooks with osascript or terminal-notifier, sounds, Codex and phones.
Updated
Claude Code can tell you when it needs you. Out of the box it sends a desktop notification in iTerm2, Ghostty and Kitty; in any other terminal, set "preferredNotifChannel": "terminal_bell" or add a Notification hook. For a banner when a task finishes, add a Stop hook that runs osascript or terminal-notifier.
Below: the built-in setting, copy-paste hooks for macOS, a sound, Codex’s version, phone pushes, and where the DIY approach runs out.
Step 1: the built-in setting
Claude Code fires a notification when a task finishes or a permission prompt is waiting and you seem to be away from the terminal. The setting is preferredNotifChannel (in /config it’s called Local notifications):
| Value | What happens |
|---|---|
auto (default) | Desktop notification in iTerm2, Ghostty and Kitty. Terminal.app rings the bell only if its audible bell is off. Nothing elsewhere |
terminal_bell | Rings the bell in any terminal |
iterm2 / iterm2_with_bell | iTerm2 notification, optionally with the bell |
kitty, ghostty | That terminal’s notification |
notifications_disabled | Nothing |
{
"preferredNotifChannel": "terminal_bell"
}
Put it in ~/.claude/settings.json. Three gotchas:
- iTerm2 needs forwarding on: Settings → Profiles → Terminal, check “Notification Center Alerts”, then Filter Alerts → “Send escape sequence-generated alerts”.
- tmux swallows notifications unless you add
set -g allow-passthrough onto~/.tmux.conf. - Warp and the VS Code terminal get no desktop notification from this setting. Use a hook.
Step 2: a Notification hook for any terminal
Hooks run a shell command at points in Claude Code’s life. The Notification event fires when Claude is waiting on you. Its matcher filters by type:
| Matcher | Fires when |
|---|---|
permission_prompt | A permission prompt has waited about 6 seconds |
idle_prompt | Claude finished about 60 seconds ago and you haven’t typed |
elicitation_dialog | An MCP server opened a form for you |
| (empty) | Every type |
The simplest version, from Anthropic’s docs, uses osascript:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
If nothing appears: osascript notifications come from Script Editor, and they fail silently until it has permission. Run osascript -e 'display notification "test"' once, then turn on Allow Notifications for Script Editor in System Settings → Notifications.
If your settings already have a hooks key, add Notification next to the other events; don’t replace the object. Type /hooks in Claude Code to check it loaded.
Better: terminal-notifier with the project name
osascript can’t tell you which agent is asking. With several sessions open, that matters. terminal-notifier (version 3.1, rebuilt on Apple’s current notification framework) can, and it replaces old banners instead of stacking them:
brew install terminal-notifier jq
Save this as ~/.claude/hooks/notify.sh and run chmod +x on it:
#!/bin/bash
# Reads the hook's JSON from stdin
input=$(cat)
msg=$(printf '%s' "$input" | jq -r '.message // "Claude Code needs you"')
project=$(basename "$(printf '%s' "$input" | jq -r '.cwd')")
terminal-notifier \
-title "Claude Code · $project" \
-message "$msg" \
-sound Glass \
-group "claude-$project" \
-activate com.apple.Terminal
Change -activate to your terminal’s bundle ID (com.googlecode.iterm2 for iTerm2, com.mitchellh.ghostty for Ghostty) so a click brings it forward. The -group flag means each project keeps one banner, not twenty.
Step 3: a Stop hook for “it’s done”
idle_prompt waits about a minute. For an alert the moment Claude finishes its turn, use the Stop event. It takes no matcher, and its input includes last_assistant_message, so the banner can say what happened:
#!/bin/bash
# ~/.claude/hooks/done.sh
input=$(cat)
summary=$(printf '%s' "$input" | jq -r '.last_assistant_message // "Done"' | tr '\n' ' ' | cut -c1-180)
project=$(basename "$(printf '%s' "$input" | jq -r '.cwd')")
terminal-notifier -title "Done · $project" -message "$summary" -group "claude-$project"
Both hooks together in ~/.claude/settings.json:
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt|idle_prompt",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify.sh" }]
}
],
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/done.sh", "async": true }]
}
]
}
}
"async": true runs the hook in the background so it never holds Claude up. One caution: Stop fires at the end of every turn, including one-line answers. If that’s too chatty, keep only idle_prompt.
Just a sound
No banner, only a chime when Claude needs you:
{
"hooks": {
"Notification": [
{
"hooks": [{ "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff" }]
}
]
}
}
Any file in /System/Library/Sounds works: Ping, Hero, Submarine. Use different sounds for permission_prompt and Stop and you can tell them apart without looking.
Codex: the notify setting
Codex has two options. Built-in terminal notifications go in ~/.codex/config.toml:
[tui]
notifications = ["agent-turn-complete", "approval-requested"]
notification_method = "auto" # or "osc9", "bel"
For a real macOS banner, notify runs your program and passes one JSON argument with type (agent-turn-complete), cwd and last-assistant-message:
notify = ["bash", "/Users/you/.codex/notify.sh"]
#!/bin/bash
# ~/.codex/notify.sh
summary=$(printf '%s' "$1" | jq -r '."last-assistant-message" // "Turn complete"' | cut -c1-180)
project=$(basename "$(printf '%s' "$1" | jq -r '.cwd')")
terminal-notifier -title "Codex · $project" -message "$summary" -group "codex-$project"
notify belongs in your user config; project-level config files ignore it.
Notifications on your phone
Desktop banners don’t help once you leave the desk. Two ways to get a push:
- Claude Code Remote Control. Start a session with
claude --remote-control, sign in to the Claude app on your phone, then turn on Push when actions required and Push when Claude decides in/config. It needs a claude.ai plan (Pro, Max, Team or Enterprise), and only covers sessions with Remote Control on. See Use Claude Code from your iPhone. - ntfy, Pushover, Slack webhooks. Swap
terminal-notifierin the scripts above for acurlto the service. It works, but the push carries your prompt text through that service.
Where DIY hooks run out
The hooks above are good, and free. Their limits:
- You can’t answer from the banner. It tells you a prompt is waiting. You still have to find the right terminal tab and press a key.
- No phone, unless you build it. And a webhook push is one-way.
- Noise with many agents. Five sessions, each with a
Stophook, means a banner every few minutes and no sense of which one waited longest. - Hooks on a server stay on the server.
osascriptdoesn’t exist on a Linux box. More in Run Claude Code over SSH and tmux. - Each agent is its own project. Claude Code, Codex and OpenCode all have different settings to maintain.
If you want to write more hooks, Claude Code hooks covers the rest of the events.
Coral: notifications you can answer
Coral is a Mac app (plus an iPhone app in beta) built for the part hooks can’t do. When an agent asks for permission, has a question or finishes, it shows up in your Mac’s notch, and you answer right there:
- Answer with one key. Press 1, 2 or 3. ⌘Z takes it back within two seconds.
- One list for every agent. Claude Code, Codex, Copilot CLI, OpenCode and Grok, the one that has waited longest first.
- One soft chime when an agent asks. Silent while you’re in Focus.
- A banner when an agent finishes, only when you can’t see the notch (menu-bar mode, locked screen, full screen), with a one-line summary.
- Your iPhone gets a push when an agent needs you or finishes while you’re away from the Mac. That’s Free. Answering from the phone is Free too, and waits on your Mac until you press Return; with Pro it reaches the agent right away.
Setup connects each agent once and shows which files change and where the backups go. If you edit or wrap Coral’s hooks, its updates leave your version alone. Details in Features, prices on Pricing.
Questions
Why don’t I get Claude Code notifications?
The default only sends desktop notifications in iTerm2, Ghostty and Kitty. In iTerm2, turn on escape-sequence alerts; in tmux, set allow-passthrough on; in other terminals, set preferredNotifChannel to terminal_bell or add a Notification hook.
How do I get a notification when Claude Code finishes?
Add a Stop hook that runs terminal-notifier or osascript. It fires every time Claude ends a turn, and the hook input includes last_assistant_message for a summary.
How do I add a notification sound to Claude Code?
Add a Notification hook whose command is afplay /System/Library/Sounds/Glass.aiff. Any sound in that folder works.
Is terminal-notifier still maintained?
Yes. Version 3.1.0 shipped in August 2026, and brew install terminal-notifier installs it. The old -sender flag was removed in 3.0; use -activate to bring your terminal forward.
Can I answer a Claude Code permission prompt from a notification?
Not with hooks: a banner can only open the terminal. Remote Control in the Claude app and Coral both let you answer, from your phone or, with Coral, from your Mac’s notch.