BlogWaiting agents and notifications

Can I get a macOS notification when Claude Code finishes or asks something?

Yes. If you run Claude Code in Ghostty, Kitty or iTerm2, it already sends a desktop notification when it finishes a task or waits on a permission prompt while you seem to be away from the terminal. In any other terminal, set preferredNotifChannel to "terminal_bell" or add a hook that runs osascript. Use a Stop or PermissionRequest hook if you want the alert immediately rather than after a delay.

What Claude Code does by default

When Claude finishes or pauses for a permission prompt and you appear to be away, Claude Code fires a notification event. The preferredNotifChannel setting decides what happens. Its default, "auto":

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

The other values are "terminal_bell", "iterm2", "iterm2_with_bell", "kitty", "ghostty" and "notifications_disabled". In /config, the setting appears as Local notifications.

Without Kanbell: set it up step by step

Option 1: the terminal bell (any terminal)

{
  "preferredNotifChannel": "terminal_bell"
}

Put this in ~/.claude/settings.json. Claude Code rings the bell character in any terminal.

Option 2: turn on iTerm2 forwarding

In iTerm2, go to Settings → Profiles → Terminal, check Notification Center Alerts, click Filter Alerts and enable Send escape sequence-generated alerts. Also check that iTerm2 has notification permission in System Settings. Ghostty and Kitty forward to the Notification Center with no setup.

Option 3: a hook with osascript

This works in any terminal, including Warp and the VS Code integrated terminal. In ~/.claude/settings.json:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

The empty matcher fires on every notification type. Hooks run alongside the built-in notification, not instead of it.

If nothing appears: osascript routes notifications through Script Editor, which fails silently without permission. Run this once, then enable Script Editor in System Settings > Notifications:

osascript -e 'display notification "test"'

Option 4: a sound

{
  "hooks": {
    "Notification": [
      {
        "hooks": [{ "type": "command", "command": "afplay /System/Library/Sounds/Glass.aiff" }]
      }
    ]
  }
}

Option 5: get the alert immediately

The Notification event is delayed on purpose:

Matcher When it fires
permission_prompt the permission prompt has waited about six seconds without typing
idle_prompt about 60 seconds after Claude finished, if you haven't typed

For an instant signal, hook the underlying events instead:

  • PermissionRequest: runs the moment Claude Code is about to ask for a tool permission.
  • PreToolUse with matcher AskUserQuestion: runs before Claude asks you a multiple-choice question.
  • Stop: runs when the main agent finishes responding (not after you interrupt it).
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Done\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Inside tmux

Add these lines to ~/.tmux.conf, then run tmux source-file ~/.tmux.conf:

set -g allow-passthrough on
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'

allow-passthrough lets notifications reach the outer terminal. The extended-keys lines fix Shift+Enter.

If you use Claude Code Desktop

The desktop app sends an OS notification when a Code session finishes and you aren't viewing it. No setup.

Limits of this method

  • Every alert looks the same. With several sessions, "Claude Code needs your attention" doesn't say which one. You can fix this by reading cwd in a script, as shown in which session is waiting.
  • A notification disappears; the wait doesn't. Dismiss it, and nothing reminds you the session is still blocked.
  • Setup per machine. Settings files, Script Editor permission, iTerm2 filters, tmux passthrough: four places that can break.
  • Claude Code only. Other agents have their own settings, or none.

With Kanbell

Kanbell is a macOS app (Apple Silicon) where you run your agents in real terminals, one window per project. It detects the waiting state with no hook to write:

  • A macOS notification when an agent needs you.
  • An orange dot that stays in the project window, in the menu bar and in the overview, for as long as the agent waits.
  • ⌘⇧J jumps to the next agent that is waiting.
  • For Claude Code, you see whether the wait is a question or a permission, or whether the agent is done. Codex CLI and Gemini CLI show their state and permission waits. Other CLI agents run without tracking.

No account and no Kanbell server are involved.

FAQ

Why don't I get notifications in iTerm2?

iTerm2 needs forwarding turned on: Settings > Profiles > Terminal, check Notification Center Alerts, then in Filter Alerts enable Send escape sequence-generated alerts.

Why don't notifications work inside tmux?

tmux swallows them unless you add set -g allow-passthrough on to ~/.tmux.conf and reload it with tmux source-file ~/.tmux.conf.

Can I just play a sound?

Yes. A Notification hook running afplay /System/Library/Sounds/Glass.aiff plays a system sound in any terminal.

Why does my osascript hook do nothing?

osascript notifications go through Script Editor. If Script Editor lacks notification permission, the command fails silently. Enable it in System Settings > Notifications.