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.
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.
Stopfires “When Claude finishes responding”. That’s the “it’s done” moment, whether the answer took ten seconds or forty minutes.Notificationfires “When Claude Code sends a notification”. Claude Code sends one in two main situations. Forpermission_prompt, the docs say the notification comes when “the prompt has waited about six seconds”. Foridle_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:
SubagentStopfires “When a subagent finishes”. It’s usually too noisy for a notification.StopFailurefires when a turn ends in an error.
Set it up for Claude Code
Claude Code reads hooks from three settings files:
~/.claude/settings.jsonapplies to all your projects..claude/settings.jsonin a repository is project settings you can commit..claude/settings.local.jsonis 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:
- Run
osascript -e 'display notification "test"'once in Terminal. - Open System Settings → Notifications.
- 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.
notifyis 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.notificationsandtui.notification_method, with methods such asosc9andbel). 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:
- Start a new session after editing the settings file.
- Ask for something quick, like “say hi”. A
Stopnotification should arrive as soon as it answers. - To test the
Notificationhook, 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:
- Check the JSON or TOML for syntax errors.
- Run the hook’s command by hand in Terminal.
- 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
- Anthropic: Hooks reference and Hooks guide for events, settings files, matchers, notification types and the osascript example and caveat.
- OpenAI: Codex advanced configuration for
notify, its payload, project-config limits and TUI notifications, and Codex hooks.
Checked on October 9, 2026. Both tools change quickly; if something here no longer matches the docs, the docs win.