NotchStack

Get notified when Claude Code or Codex finishes

Stop watching the terminal. Here's how to get a Mac notification, or a sound, the moment Claude Code finishes a task or needs your permission, and how to do the same for Codex.

Updated 6 min readBy the NotchStack team

The short answer

Claude Code supports hooks: shell commands it runs when something happens. Add a hook for the Stop event (“When Claude finishes responding”) and one for Notification (“When Claude Code sends a notification”), and have each run osascript to show a macOS notification. Put them in ~/.claude/settings.json so they apply to every project.

Codex CLI has a notify setting in ~/.codex/config.toml. It runs a program of your choice with a JSON payload when a turn completes (event type agent-turn-complete).

Both setups are below, ready to paste. Then we cover the details that trip people up, and how NotchStack turns this into a card that drops from your notch.

Stop vs Notification: which event you want

Claude Code has two hook events that matter here. Most guides only use one of them.

  • Stop fires “When Claude finishes responding”. That’s the “it’s done” moment, whether the answer took ten seconds or forty minutes.
  • Notification fires “When Claude Code sends a notification”. Claude Code sends one in two main situations. For permission_prompt, the docs say the notification comes when “the prompt has waited about six seconds”. For idle_prompt, it comes when “Claude finished responding about 60 seconds ago and you haven’t typed since”.

If you only hook Notification, you hear about permission prompts quickly, but a finished task can take a minute to reach you. If you only hook Stop, you’ll miss the moments when Claude is blocked waiting for you to approve a command. Use both.

Two more events are worth knowing:

  • SubagentStop fires “When a subagent finishes”. It’s usually too noisy for a notification.
  • StopFailure fires when a turn ends in an error.

Set it up for Claude Code

Claude Code reads hooks from three settings files:

  • ~/.claude/settings.json applies to all your projects.
  • .claude/settings.json in a repository is project settings you can commit.
  • .claude/settings.local.json is project settings just for you.

For notifications, the user-level file is the right place. Open ~/.claude/settings.json (create it if it doesn’t exist) and add a hooks block:

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

The Notification entry follows the example in Anthropic’s hooks guide. An empty matcher means “match all”, as does "*" or leaving it out.

The Stop entry has no matcher on purpose. Stop doesn’t support matchers. The docs warn that a matcher on an event without matcher support “is silently ignored”.

If the file already has settings, merge the hooks key into the existing object; don’t paste a second top-level object. A JSON syntax error is the most common reason hooks “don’t work”.

Prefer a sound?

Swap the osascript command for afplay, which plays any audio file. macOS ships a few short system sounds in /System/Library/Sounds/:

"command": "afplay /System/Library/Sounds/Glass.aiff"

You can also do both, with a notification for permission prompts and a sound when a task finishes.

Include what Claude actually said

A plain “Claude finished” is useful. Knowing which session finished, and what it said, is better.

Hooks receive JSON on standard input. For Stop, the docs say hooks receive stop_hook_active and last_assistant_message, among other fields. A small script can read that JSON and put the first line of the message in the notification. Save this as ~/.claude/notify-done.sh and make it executable (chmod +x):

#!/bin/bash
# Reads the Stop hook's JSON from stdin and shows the start of Claude's last message.
input=$(cat)
msg=$(printf '%s' "$input" | jq -r '.last_assistant_message // "Claude finished" | split("\n")[0] | .[0:120]')
dir=$(basename "$(printf '%s' "$input" | jq -r '.cwd // "."')")
osascript -e "display notification \"${msg//\"/\\\"}\" with title \"Claude Code · $dir\""

Then point the Stop hook’s command at ~/.claude/notify-done.sh. The script uses jq to read JSON. If jq isn’t on your Mac, install it with Homebrew (brew install jq).

The silent failure: Script Editor permission

Anthropic’s own guide flags this one. osascript sends notifications through the built-in Script Editor app, and “if Script Editor doesn’t have notification permission, the command fails silently”. Nothing errors; you just never see a banner.

To fix it:

  1. Run osascript -e 'display notification "test"' once in Terminal.
  2. Open System Settings → Notifications.
  3. Find Script Editor and allow notifications. Banners are the least intrusive style.

If you use Focus modes, make sure Script Editor is allowed through the ones you work in. Otherwise the alert lands in Notification Center without a banner.

Set it up for Codex

Codex CLI has a top-level notify setting. It runs a program when a supported event happens. According to OpenAI’s config reference, that is “currently only agent-turn-complete”.

The program gets a single JSON argument. It includes type, thread-id, turn-id, cwd, input-messages and last-assistant-message.

Add this to ~/.codex/config.toml:

notify = ["python3", "/Users/you/.codex/notify.py"]

And a minimal notify.py:

#!/usr/bin/env python3
import json, subprocess, sys

event = json.loads(sys.argv[1])
if event.get("type") == "agent-turn-complete":
    msg = (event.get("last-assistant-message") or "Codex finished").splitlines()[0][:120]
    msg = msg.replace('"', '\\"')
    subprocess.run(["osascript", "-e", f'display notification "{msg}" with title "Codex"'])

Three things to know:

  • Use your home config. notify is one of the keys Codex ignores in a project-local .codex/config.toml, so it has to live in ~/.codex/config.toml.
  • There’s only one notify. It’s a single top-level setting. If you already have a notify program, chain the two inside one script rather than adding a second line.
  • Codex has built-in alerts too. The TUI has its own notification settings (tui.notifications and tui.notification_method, with methods such as osc9 and bel). They work through your terminal, so they depend on the terminal app supporting them.

Codex also has its own hooks system with a Stop event, configured in ~/.codex/hooks.json. Unlike notify, non-managed hooks “must be reviewed and trusted before they run”. For a simple done-alert, notify is less setup.

Testing your setup

For Claude Code:

  1. Start a new session after editing the settings file.
  2. Ask for something quick, like “say hi”. A Stop notification should arrive as soon as it answers.
  3. To test the Notification hook, ask Claude to run a command it needs permission for and wait about six seconds.

For Codex, send any prompt and let the turn finish.

If nothing appears:

  1. Check the JSON or TOML for syntax errors.
  2. Run the hook’s command by hand in Terminal.
  3. Check the Script Editor permission described above.

Getting the alert in your notch instead

macOS banners pile up in Notification Center and look the same as every other alert. If you have NotchStack, it can do this for you, in the notch.

Turn it on under Settings → Agents. NotchStack then adds two small hooks, Stop (done) and Notification (needs you), to ~/.claude/settings.json, and one notify line to ~/.codex/config.toml. If Codex already has a notify program, NotchStack leaves the file alone and tells you.

When an agent finishes, a card drops from the notch with:

  • the agent’s logo
  • the project name
  • how long it worked
  • the agent’s last message, plus a button to jump back to your terminal

If you’re already looking at the notch panel, the event shows as a one-line status instead. Turning the feature off removes only NotchStack’s own lines.

Sources

Checked on October 9, 2026. Both tools change quickly; if something here no longer matches the docs, the docs win.