# Claude Code Notification When Done: Sound and Alerts

> Get a Claude Code notification when done: built-in desktop alerts, a Stop hook that plays a sound, and permission alerts on macOS. Copy-paste settings.json.

Canonical: https://usecactus.app/guides/claude-code-notification-when-done
Last updated: 2026-10-17

Claude Code can alert you when it finishes or needs permission, and a two-line hook adds a sound. Set it up once, then step away from the screen without opening a tab to pass the time.

## How do you get a Claude Code notification when it's done?

Claude Code already sends a notification when it finishes a task or waits on a permission prompt and you appear to be away from the terminal. In iTerm2, Ghostty, and Kitty that's a desktop notification by default. In other terminals, set `preferredNotifChannel` to `"terminal_bell"`. For a sound every time Claude finishes, add a `Stop` hook that runs `afplay`.

The quick version for macOS:

1. Open `~/.claude/settings.json`. Create it if it doesn't exist.
2. Paste the settings from [the combined config](https://usecactus.app/guides/claude-code-notification-when-done#the-combined-config-to-copy) below.
3. Type `/hooks` in Claude Code and confirm the hooks appear under `Stop` and `Notification`.
4. Give Claude a small task, switch to another window, and wait for the sound.

Everything below comes from Anthropic's [terminal configuration](https://code.claude.com/docs/en/terminal-config#get-a-terminal-bell-or-notification), [hooks guide](https://code.claude.com/docs/en/hooks-guide#get-notified-when-claude-needs-input), and [hooks reference](https://code.claude.com/docs/en/hooks#notification), as of October 2026.

## Which event should trigger the alert?

Claude Code has more than one moment worth hearing about, and each one maps to a different setting.

| You want to know when | Use | When it fires |
| --- | --- | --- |
| Claude finished and you stepped away | Built-in notification | When Claude finishes or a permission prompt waits, and you appear to be away |
| Claude finished, every time | `Stop` hook | Each time the main agent finishes responding. Not after you interrupt it with `Esc` |
| Claude needs approval | `Notification` hook, `permission_prompt` | After a permission prompt has waited about six seconds without typing |
| Claude has been idle a while | `Notification` hook, `idle_prompt` | About 60 seconds after Claude finishes, if you haven't typed since |

For "tell me when it's done", the `Stop` hook is the one most people want. The built-in notification is quieter and only fires when Claude Code thinks you're away.

## Option 1: Turn on the built-in notification

No hooks needed. Claude Code reads the `preferredNotifChannel` setting, which shows up in `/config` as **Local notifications**. The values are:

- `"auto"`, the default. Desktop notification in iTerm2, Ghostty, and Kitty. In Apple's Terminal it rings the bell only when Terminal's audible bell is off. Elsewhere, nothing.
- `"terminal_bell"`. Rings the bell in any terminal.
- `"iterm2"`, `"iterm2_with_bell"`, `"kitty"`, `"ghostty"`. Force a specific terminal's notification.
- `"notifications_disabled"`. No notification. Hooks still run.

If you use Apple's Terminal, Warp, or the VS Code terminal, ring the bell:

```json
{
  "preferredNotifChannel": "terminal_bell"
}
```

Then make the bell do something you'll notice, as described in [terminal bell settings](https://usecactus.app/guides/claude-code-notification-when-done#make-the-terminal-bell-noticeable).

If notifications don't appear in iTerm2, open **Settings > Profiles > Terminal**, check **Notification Center Alerts**, click **Filter Alerts**, and enable **Send escape sequence-generated alerts**. Also check that your terminal app is allowed to send notifications in **System Settings > Notifications**.

Running Claude Code inside tmux? Notifications don't reach the outer terminal unless you add `set -g allow-passthrough on` to `~/.tmux.conf` and run `tmux source-file ~/.tmux.conf`.

## Option 2: Play a sound when Claude Code finishes

A `Stop` hook runs a command each time the main agent finishes responding. On macOS, `afplay` plays a sound file, and the system sounds live in `/System/Library/Sounds`.

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Glass.aiff",
            "async": true
          }
        ]
      }
    ]
  }
}
```

`"async": true` runs the sound in the background so it doesn't hold up the end of the turn. Run `ls /System/Library/Sounds` to pick a different sound.

Two details from the hooks reference matter here. `Stop` doesn't fire when you interrupt Claude yourself, which is what you want. API errors fire a separate `StopFailure` event, so a run that dies on a rate limit stays silent unless you add a `StopFailure` hook too.

## Option 3: Get alerted when Claude needs permission

The worst wait is the one where Claude stopped two minutes ago to ask "May I run this?" and you didn't notice. A `Notification` hook with the `permission_prompt` matcher covers it. This one posts a macOS notification with `osascript`:

```json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude needs your permission\" with title \"Claude Code\" sound name \"Glass\"'"
          }
        ]
      }
    ]
  }
}
```

Use `"matcher": "idle_prompt"` instead for a reminder about a minute after Claude finishes, or leave the matcher out to run on every notification type. Hooks run alongside the built-in notification, not instead of it.

The delay exists because these notifications are meant for when you're away, and each keystroke pushes it back. If you want a hook to run the instant Claude asks for permission, Anthropic's docs point to the `PermissionRequest` event instead.

### If the osascript notification never shows up

`osascript` notifications come from the built-in Script Editor app. If Script Editor doesn't have notification permission, the command fails silently and macOS doesn't ask. Fix it once:

1. Run `osascript -e 'display notification "test"'` in Terminal. Nothing appears yet.
2. Open **System Settings > Notifications** and find **Script Editor**.
3. Turn on **Allow Notifications**.
4. Run the command again and confirm the test notification appears.

If a Focus such as Do Not Disturb is on, macOS may hold the notification back. The `afplay` sound plays regardless.

## The combined config to copy

This is the setup I'd start with on a Mac: a sound when Claude finishes, a notification when it needs permission, and the bell for terminals without built-in notifications. Paste it into `~/.claude/settings.json`. If the file already has settings, merge the `hooks` keys into your existing object, because JSON allows only one top-level object and no comments.

```json
{
  "preferredNotifChannel": "terminal_bell",
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "afplay /System/Library/Sounds/Glass.aiff",
            "async": true
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude needs your permission\" with title \"Claude Code\" sound name \"Glass\"'"
          }
        ]
      }
    ]
  }
}
```

Drop the `preferredNotifChannel` line if you use iTerm2, Ghostty, or Kitty and like their desktop notifications. To share the setup with a team, put it in the project's `.claude/settings.json` instead, which you can commit.

## Show which project finished

With several sessions running, "Claude is done" isn't enough. Hooks receive JSON on stdin, including `cwd`, the session's working directory. This script turns it into a notification that names the project. Save it as `~/.claude/hooks/notify-done.sh` and run `chmod +x ~/.claude/hooks/notify-done.sh`:

```bash
#!/bin/bash
input=$(cat)
project=$(basename "$(jq -r '.cwd' <<<"$input")")
osascript \
  -e 'on run argv' \
  -e 'display notification ("Finished in " & item 1 of argv) with title "Claude Code" sound name "Glass"' \
  -e 'end run' \
  "$project"
```

The project name goes in as an argument, so a folder name with quotes can't break the AppleScript. The script uses `jq`. Run `which jq` to check, and install it with Homebrew if it's missing. Then point the `Stop` hook at the script:

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/notify-done.sh", "async": true }
        ]
      }
    ]
  }
}
```

## Make the terminal bell noticeable

A bell only helps if it does something you notice from across the room. These settings work for any agent that rings the bell, not just Claude Code.

**Apple's Terminal.** Open **Terminal > Settings > Profiles**, pick your profile, and open **Advanced**. Under Bell, turn on **Audible bell**, and optionally **Bounce the app icon when in the background** or **Badge app and window Dock icons**. Apple documents these in its [Terminal advanced settings guide](https://support.apple.com/guide/terminal/change-profiles-advanced-settings-trmladvn/mac). If you accepted Claude Code's first-run terminal setup prompt, it turned the audible bell off in your Terminal profile, so turn it back on.

**iTerm2.** In **Settings > Profiles > Terminal**, make sure **Silence bell** is off. **Show bell icon in tabs** marks which session rang, and **Notification Center Alerts** can post a notification for a bell, per [iTerm2's documentation](https://iterm2.com/documentation-preferences-profiles-terminal.html).

**Ghostty.** Set `bell-features` in your config file. The `system` feature plays the macOS alert sound, `attention` bounces the Dock icon once, and `audio` plays a custom file set with `bell-audio-path`. For example:

```
bell-features = system,attention
```

See the [Ghostty config reference](https://ghostty.org/docs/config/reference) for the full list.

## Using Codex too?

Codex has its own settings in `~/.codex/config.toml`. Its built-in terminal notifications live under `[tui]`, and `notification_method` accepts `auto`, `osc9`, or `bel`:

```toml
[tui]
notifications = true
notification_method = "bel"
```

For a custom command, Codex's `notify` setting runs an external program on `agent-turn-complete`. OpenAI documents both in its [advanced config page](https://learn.chatgpt.com/docs/config-file/config-advanced). For any other agent or a long build, append a sound to the command itself: `npm test; afplay /System/Library/Sounds/Glass.aiff`.

## Why a notification helps you focus

Without a notification, you have two choices during a long run. Watch the terminal, or open something else and check back. The second one is where the time goes, because the tab you open to fill a minute has no reason to close when the agent finishes.

A sound changes that. You can stand up, refill your water, read the docs for the next step, and come back when you hear it. For more ideas on using the gap, see [what to do while Claude Code is working](https://usecactus.app/guides/what-to-do-while-claude-code-is-working).

The notification solves getting back. It doesn't stop the drift to YouTube or X in the first place. Cactus for Mac covers that half: you set one recurring schedule, such as weekdays from 9:00 to 18:00, add the sites you drift to, and they stay blocked in every browser during those hours. An active block can't be ended early. Browser secure DNS, iCloud Private Relay, and some VPNs can affect blocking, so test your setup. Cactus doesn't connect to Claude Code and sends no notifications itself, so the hooks above and a scheduled block do separate jobs. It blocks websites only, not apps, and a Mac administrator can still remove it. The [coding agents page](https://usecactus.app/for-coding-agents) and [how Cactus blocks websites](https://usecactus.app/how-cactus-blocks) have the details, and the [developer blocklist](https://usecactus.app/guides/distracting-websites-to-block-developers) lists the exact hostnames to add.

## Questions

### How do I make Claude Code play a sound when it finishes?

Add a Stop hook to ~/.claude/settings.json that runs afplay /System/Library/Sounds/Glass.aiff. The Stop event fires every time the main agent finishes responding.

### Why doesn't Claude Code notify me when it's done?

The built-in notification fires only when you appear to be away from the terminal, and by default it sends desktop notifications only in iTerm2, Ghostty, and Kitty. In other terminals, set preferredNotifChannel to terminal_bell or add a hook.

### Why does my osascript notification never appear?

osascript notifications come from Script Editor. Run osascript -e 'display notification "test"' once, then turn on Allow Notifications for Script Editor in System Settings > Notifications.

### Can Claude Code notify me when it needs permission?

Yes. A Notification hook with the permission_prompt matcher runs when a permission prompt has waited about six seconds without you typing.

### Does Cactus send a notification when Claude Code finishes?

No. Cactus has no notifications and doesn't watch your agent. It blocks the websites you choose during scheduled hours, so it pairs with a Claude Code hook rather than replacing one.

## Keep reading

- [The most distracting websites to block, for developers](https://usecactus.app/guides/distracting-websites-to-block-developers): The most distracting websites to block as a developer, grouped by type, with the exact hostnames to add and the sites to keep open: docs, GitHub, registries.
- [What to do while Claude Code is working](https://usecactus.app/guides/what-to-do-while-claude-code-is-working): What to do while Claude Code is working: review the plan, queue the next prompt, run a second agent, or stand up. And why opening YouTube costs 20 minutes.
- [How to block social media on your Mac](https://usecactus.app/guides/block-social-media-on-mac): Block social media on your Mac or MacBook: the exact domains for 12 networks, how to block them in Safari with Screen Time, and how to block them on a schedule.
