Claude Code notification setup

How to get a Claude Code notification when it's done

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 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, hooks guide, and hooks reference, 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 whenUseWhen it fires
Claude finished and you stepped awayBuilt-in notificationWhen Claude finishes or a permission prompt waits, and you appear to be away
Claude finished, every timeStop hookEach time the main agent finishes responding. Not after you interrupt it with Esc
Claude needs approvalNotification hook, permission_promptAfter a permission prompt has waited about six seconds without typing
Claude has been idle a whileNotification hook, idle_promptAbout 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:

{
  "preferredNotifChannel": "terminal_bell"
}

Then make the bell do something you'll notice, as described in terminal bell settings.

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.

{
  "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:

{
  "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.

{
  "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:

#!/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:

{
  "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. 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.

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 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:

[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. 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.

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 and how Cactus blocks websites have the details, and the developer blocklist lists the exact hostnames to add.

Questions

Maxi, the founder of Cactus, working at his desk

Why I made Cactus

I couldn't stop myself.

I kept opening YouTube and X while waiting for coding agents.

A 20-second pause could turn into 20 lost minutes.

I built Cactus so those sites simply do not open during the hours I chose.

Seven days free. No card. A license is $19.99 USD once. Link handles checkout. By buying, you agree to the Terms and acknowledge the Privacy Policy.

Cactus runs on Mac

Open usecactus.app on your Mac to download the free trial. It lasts seven days and needs no account or card.

See pricing