Desktop notifications for AI coding agents — never miss a completion, approval, or error again.
Quick Start • Features • Why agent-notify? • Compatibility • Usage • Configuration • FAQ
brew tap paultendo/agent-notify https://github.com/paultendo/agent-notify
brew install agent-notify
agent-notify --setupchmod +x ./install-agent-notify.sh
./install-agent-notify.sh
agent-notify --setup # configure all detected agentsPlatform recommendations & individual agent setup
Configure agents individually:
agent-notify --setup-codex # Codex CLI → ~/.codex/config.toml
agent-notify --setup-claude # Claude Code → ~/.claude/settings.json
agent-notify --setup-gemini # Gemini CLI → ~/.gemini/settings.jsonPlatform-specific packages:
# macOS
brew install terminal-notifier
# Linux (Debian/Ubuntu)
sudo apt install libnotify-bin # notify-send
sudo apt install espeak # optional TTS
# Linux (Fedora)
sudo dnf install libnotify # notify-send
sudo dnf install espeak-ng # optional TTSTip
Restart your agent after setup and run a task to verify notifications are working.
Note
macOS click-to-focus requires terminal-notifier (brew install terminal-notifier). For per-window focus (focusing the correct VS Code window, not just VS Code), grant accessibility access to terminal-notifier.app in System Settings → Privacy & Security → Accessibility.
- Multi-agent — Codex CLI, Claude Code, and Gemini CLI out of the box.
- Cross-platform — macOS, Linux, and Windows (WSL / Git Bash).
- Zero dependencies — pure Bash + Python 3 (already on your system).
- One-command setup —
agent-notify --setupconfigures all detected agents. - Homebrew installable —
brew install agent-notify. - Self-update —
agent-notify --updatepulls the latest release.
- Event categories — completion, approval, question, error, and auth events with distinct sounds.
- Git branch display — shows current branch in notification subtitle.
- Duration display — shows how long the task took.
- Long-run threshold — only notify if the task exceeded N seconds (
CODEX_NOTIFY_MIN_DURATION). - Rich titles/messages extracted from agent JSON payloads.
- Clean, grouped notifications per session/thread.
- Click-to-focus — auto-detects your terminal and focuses the right window on click. Supports 10 terminals: VS Code, iTerm2, Ghostty, Warp, kitty, WezTerm, Alacritty, Hyper, Terminal.app, and the Codex macOS app.
- Multiplexer support — tmux, zellij, kitty, and WezTerm pane/window focus on notification click.
- Terminal bell — automatic fallback for headless/SSH sessions.
- Terminal echo: prints a summary line to stderr for logging/visibility.
- Stdout safety — never writes to stdout during hook execution, safe for Claude Code and Gemini CLI.
- Slack — rich attachments with color-coded event categories.
- Discord — embeds with color and emoji.
- Telegram — bot messages with chat ID.
- ntfy — priority-based push notifications.
- Generic JSON — works with any webhook endpoint.
- All free, all auto-detected from the URL.
- Formatting follows the destination. An agent's markdown becomes Slack mrkdwn and Telegram HTML, and Discord gets it as written. Desktop notifications, speech and ntfy (on phones) get plain text, the terminal shows real bold, and the generic JSON and your own hook get the text as the agent wrote it, with
title_plainandmessage_plainalongside.
- Do Not Disturb / Focus awareness — skip notifications when Focus mode is active.
- Schedule window — only notify during configured hours.
- Rate limiting — throttle rapid-fire notifications.
- Per-project config —
.agent-notify.envoverrides per repo. - TTS (text-to-speech) — via macOS
say, Linuxespeak/spd-say, or Windows SAPI. - Notification log — append history to
~/.codex/notify.log. - Custom hooks — run any command on notification events.
- macOS: fallback to
osascriptifterminal-notifieris missing. - Linux: fallback chain
notify-send→zenity→ terminal echo. - Windows: PowerShell toast notifications via WSL/Git Bash.
| agent-notify | heyagent | claude-code-notification | cfngc4594/codex-notify | |
|---|---|---|---|---|
| Agents supported | Codex CLI, Claude Code, Gemini CLI | Claude Code only | Claude Code only | Codex CLI only |
| Platforms | macOS, Linux, WSL | macOS only | macOS, Linux | macOS, Linux |
| Zero dependencies | Yes | No (Node.js) | No (Node.js) | Yes |
| Smart event categories | 5 (completion, approval, question, error, auth) | — | — | — |
| Terminal click-to-focus | 10 terminals + 4 multiplexers | — | — | — |
| Webhook integrations | 5 services (all free) | — | — | — |
| Per-project config | Yes | — | — | — |
| One-command setup | --setup for all agents |
Manual | Manual | Manual |
| Self-update | --update |
npm | npm | — |
| Agent | Hook type | Config file | Events |
|---|---|---|---|
| Codex CLI | argv JSON | ~/.codex/config.toml |
agent-turn-complete, approval-required |
| Claude Code | stdin JSON | ~/.claude/settings.json |
Stop, Notification (permission, idle, auth) |
| Gemini CLI | stdin JSON | ~/.gemini/settings.json |
AfterAgent, Notification (tool permission) |
Event categories
| Category | Trigger | Sound | Examples |
|---|---|---|---|
| completion | Task finished | Glass / complete | Stop, AfterAgent, agent-turn-complete |
| approval | Permission needed | Sosumi / dialog-warning | permission_prompt, ToolPermission |
| question | User input needed | Sosumi / dialog-warning | idle_prompt, elicitation_dialog |
| error | Session/API error | Sosumi / dialog-warning | Session limit, 401, API overload |
| auth | Auth event | Glass / complete | auth_success |
Platforms
| Platform | Notification | Sound | TTS |
|---|---|---|---|
| macOS | terminal-notifier / osascript | afplay | say |
| Linux | notify-send / zenity | paplay / aplay / ffplay | espeak / spd-say |
| Windows (WSL) | PowerShell toast | PowerShell SoundPlayer | PowerShell SAPI |
Terminal click-to-focus (macOS)
| Terminal | Detection | Focus method |
|---|---|---|
| VS Code | TERM_PROGRAM / default |
AppleScript window match |
| iTerm2 | TERM_PROGRAM / ITERM_SESSION_ID |
AppleScript window match |
| Ghostty | GHOSTTY_RESOURCES_DIR |
AppleScript window match |
| Warp | __CFBundleIdentifier / TERM_PROGRAM |
AppleScript window match |
| kitty | KITTY_WINDOW_ID + KITTY_LISTEN_ON |
kitty @ focus-window / AppleScript fallback |
| WezTerm | WEZTERM_PANE |
wezterm cli activate-pane / AppleScript fallback |
| Alacritty | TERM_PROGRAM |
AppleScript window match |
| Terminal.app | TERM_PROGRAM |
AppleScript window match |
Multiplexer support (macOS)
| Multiplexer | Detection | Click action |
|---|---|---|
| tmux | $TMUX |
tmux select-window + select-pane (with socket path) |
| zellij | $ZELLIJ |
zellij action go-to-tab-name (via layout dump) |
| kitty | $KITTY_WINDOW_ID |
kitty @ focus-window --match id:N |
| WezTerm | $WEZTERM_PANE |
wezterm cli activate-pane --pane-id N |
bash4.0+ (macOS, Linux, WSL, or Git Bash).python3for JSON payload parsing and agent setup.- macOS:
terminal-notifierrequired for click-to-focus (brew install terminal-notifier). - Linux:
libnotify(notify-send) recommended.
Manual invocation (title/message):
agent-notify "Codex" "Task finished"Manual invocation (Codex JSON payload):
agent-notify '{"type":"agent-turn-complete","last-assistant-message":"All set","input-messages":["ping"],"cwd":"/tmp","thread-id":"demo"}'Test notifications:
agent-notify --test # Codex completion
agent-notify --test-approval # Codex approval (Sosumi)
agent-notify --test-claude # Claude Code completion
agent-notify --test-claude-approval # Claude Code permission prompt
agent-notify --test-gemini # Gemini CLI completion
agent-notify --test-say # completion with TTS
agent-notify --test-bell # terminal bell
agent-notify --update # update to latest releaseEnvironment variables
| Variable | Default | Description |
|---|---|---|
CODEX_NOTIFY_SOUND |
Glass / complete |
Completion sound (path or system name) |
CODEX_NOTIFY_APPROVAL_SOUND |
Sosumi / dialog-warning |
Approval sound |
CODEX_SILENT=1 |
— | Disable all sounds |
CODEX_NOTIFY_QUIET=1 |
— | Suppress terminal echo |
CODEX_ACTIVATE_BUNDLE |
auto-detected | macOS: app to activate on click |
CODEX_SENDER_BUNDLE |
com.microsoft.VSCode |
macOS: sender icon for -activate mode |
CODEX_SUPPRESS_FRONTMOST=0 |
1 |
Disable suppression when target app is frontmost |
CODEX_NOTIFY_EVENT_TYPES |
* |
Codex event types to handle (comma-separated) |
CODEX_NOTIFY_EXEC_ONLY=0 |
1 |
macOS: use -activate instead of -execute |
CODEX_NOTIFY_APP_ICON |
— | Custom icon path or URL |
CODEX_NOTIFY_SAY=1 |
— | Enable text-to-speech |
CODEX_NOTIFY_SAY_VOICE |
system | TTS voice name |
CODEX_NOTIFY_SAY_RATE |
system | TTS speech rate (wpm, macOS only) |
CODEX_NOTIFY_WEBHOOK |
— | Webhook URL (auto-detects service) |
CODEX_NOTIFY_WEBHOOK_PRESET |
auto | Webhook format: slack, discord, telegram, ntfy, generic |
CODEX_NOTIFY_TELEGRAM_CHAT_ID |
— | Telegram chat ID (required for telegram preset) |
CODEX_NOTIFY_NTFY_TOPIC |
— | ntfy topic (can also be in URL) |
CODEX_NOTIFY_NTFY_MARKDOWN |
0 |
1 sends ntfy messages as markdown. ntfy renders it in its web app only; its phone apps would show the markers, so messages are plain text by default |
CODEX_NOTIFY_WEBHOOK_CATEGORIES |
all | Categories sent to the webhook, comma-separated: completion, approval, question, error, auth. approval,question,error makes a phone buzz only when you're needed |
CODEX_NOTIFY_AGENT_NAME |
— | Your name for this agent, shown in titles, to tell agents running side by side apart (set it per project) |
CODEX_NOTIFY_EXEC_CMD |
auto-detected | Override the click-execute command |
CODEX_NOTIFY_DND=1 |
— | Skip notifications during Focus/DND |
CODEX_NOTIFY_SCHEDULE |
— | Notify only during HH:MM-HH:MM window |
CODEX_NOTIFY_THROTTLE |
0 |
Suppress notifications within N seconds |
CODEX_NOTIFY_LOG=1 |
— | Append to ~/.codex/notify.log |
CODEX_NOTIFY_LOG_FILE |
~/.codex/notify.log |
Override log file path |
CODEX_NOTIFY_HOOK |
— | Command to run on each event |
CODEX_NOTIFY_DEBUG=1 |
— | Show debug output |
CODEX_NOTIFY_BELL |
auto |
Terminal bell: 0/1/auto |
CODEX_NOTIFY_MIN_DURATION |
0 |
Only notify if N+ seconds elapsed |
CODEX_NOTIFY_ACTIVATE_CMD |
— | Linux/Windows: command to focus editor |
CODEX_NOTIFY_GIT_BRANCH |
1 |
Show git branch in notification subtitle |
# Slack — auto-detected from URL, sends rich attachment with color
export CODEX_NOTIFY_WEBHOOK="https://hooks.slack.com/services/T.../B.../xxx"
# Discord — auto-detected from URL, sends embed with color + emoji
export CODEX_NOTIFY_WEBHOOK="https://discord.com/api/webhooks/123/abc"
# Telegram — set chat ID, URL auto-detected
export CODEX_NOTIFY_WEBHOOK="https://api.telegram.org/bot<TOKEN>/sendMessage"
export CODEX_NOTIFY_TELEGRAM_CHAT_ID="-1001234567890"
# ntfy — topic extracted from URL, priority based on event category
export CODEX_NOTIFY_WEBHOOK="https://ntfy.sh/my-agent-notifications"
# Generic JSON — works with any webhook endpoint
export CODEX_NOTIFY_WEBHOOK="https://example.com/webhook"Create a .agent-notify.env file in your project root to override settings per repo:
# .agent-notify.env
CODEX_NOTIFY_SOUND=Funk
CODEX_NOTIFY_SAY=1
CODEX_NOTIFY_MIN_DURATION=30Any CODEX_NOTIFY_* or CODEX_SILENT variable can be set. The file is sourced when the notification fires (the agent passes the project's cwd in the payload). The legacy .codex-notify.env filename is also supported.
- How does terminal detection work? Agent-notify reads
TERM_PROGRAM,__CFBundleIdentifier, and multiplexer env vars (TMUX,ZELLIJ,KITTY_WINDOW_ID,WEZTERM_PANE) to auto-detect your terminal and build the right focus command. Override withCODEX_ACTIVATE_BUNDLE. - Does it work with tmux? Yes. Clicking the notification will select the correct tmux window and pane. It uses the tmux socket path for reliable targeting across sessions.
- Does it work with the Codex macOS app? Yes. If the Codex macOS app (
com.openai.codex) is frontmost when the notification fires, clicking it will activate the Codex app instead of VS Code. This is automatic — no configuration needed. - Does it work over SSH? Yes. Terminal bell (
CODEX_NOTIFY_BELL=auto) fires automatically when no display server is detected. Many terminal emulators convert the bell to a native notification. - Can I use different settings per project? Yes. Drop a
.agent-notify.envfile in any project root. See Per-project configuration above. - Which webhook services are supported? Slack, Discord, Telegram, and ntfy have first-class formatters with rich messages, colors, and emoji. Any other URL receives a generic JSON payload. The service is auto-detected from the URL.
Troubleshooting
- No notification: check agent config files and OS notification permissions.
- No sound: check
CODEX_SILENTandCODEX_NOTIFY_SOUND(path or system sound name). - macOS: click does not activate editor: install
terminal-notifierand verifyCODEX_ACTIVATE_BUNDLE. - macOS: icon looks like Terminal: this is expected with execute-only activation. Cosmetic only.
- Linux: no notification: install
libnotify-bin(Debian/Ubuntu) orlibnotify(Fedora). - Seeing a
terminal-notifierusage banner: make sure yournotifyhook points toagent-notifyand setCODEX_NOTIFY_DEBUG=1to inspect args. - Clicking "Show" opens Script Editor: set
CODEX_NOTIFY_EXEC_CMD="/usr/bin/open -b com.microsoft.VSCode". - Wrong terminal focused on click: set
CODEX_ACTIVATE_BUNDLEto your terminal's bundle ID, or check thatTERM_PROGRAMis set correctly in your shell.
- Terminal auto-detection: on macOS, agent-notify detects your terminal from
TERM_PROGRAM,__CFBundleIdentifier, and multiplexer environment variables. It builds the right click-to-focus command automatically — no configuration needed. - tmux support: when running inside tmux, clicking the notification focuses the correct tmux window and pane, using the socket path for reliable targeting.
- Git branch: shown in the notification subtitle by default. Disable with
CODEX_NOTIFY_GIT_BRANCH=0. - Some macOS versions ignore
-appIconand use the sender app icon instead. - If
terminal-notifieris missing on macOS, the script falls back toosascript. - Without
python3, JSON payloads produce a clear error; manual title/message mode still works.
Note
The script never writes to stdout during hook execution, so it's safe for Claude Code and Gemini CLI which parse hook stdout.
- All data stays local by default. Webhook support (
CODEX_NOTIFY_WEBHOOK) is opt-in and sends notification title/message to the configured URL. - Payload is read from stdin/args and used only to build notification text.
- Per-project
.agent-notify.envonly processes lines matchingCODEX_NOTIFY_*orCODEX_SILENTfor safety.
Changelog
- 1.2.0 — Formatting follows the destination: plain text on the desktop, in speech and on phones, real bold in the terminal, Slack mrkdwn, Telegram HTML and markdown for Discord; the generic webhook and your own hook get the text as written, with
title_plainandmessage_plain. ntfy no longer shows raw JSON.CODEX_NOTIFY_WEBHOOK_CATEGORIESandCODEX_NOTIFY_AGENT_NAME. Per-window focus for all terminals.--setup-codexputs the hook at the root of the config and repairs configs an older version broke. An event passed as an argument no longer waits on stdin. Licence: AGPL-3.0-only (1.1.0 and earlier were MIT). - 1.1.0 — Smart event categories (completion, approval, question, error, auth). Git branch display. Terminal auto-detection (iTerm2, Ghostty, Warp, kitty, WezTerm, Alacritty). Multiplexer support (tmux, zellij). Rich webhook formatters (Slack, Discord, Telegram, ntfy). Session limit and API error detection.
- 1.0.0 — Cross-platform (macOS, Linux, Windows WSL). Multi-agent (Codex CLI, Claude Code, Gemini CLI). Terminal bell, long-run threshold, duration display, per-project config. Renamed from codex-notify to agent-notify.
- 0.6.0 — DND/Focus awareness, schedule window, rate limiting, notification log, custom hooks,
--update, Homebrew formula. - 0.4.0 — TTS support (
say), webhook notifications,--test-say. - 0.3.0 — Differentiated completion/approval sounds,
--setup,--test-approval, default to all event types. - 0.2.0 — Terminal echo,
--version/--help/--testflags, async sound, python3 guard. - 0.1.x — Initial releases, sound customization, screenshots.
AGPL-3.0-only. See LICENSE. Releases up to and including v1.1.0 were published under the MIT licence.
- Remove hooks from your agent config files:
- Codex CLI: remove
notifyline from~/.codex/config.toml - Claude Code: remove hooks from
~/.claude/settings.json - Gemini CLI: remove hooks from
~/.gemini/settings.json
- Codex CLI: remove
- Delete the script:
rm ~/bin/agent-notify - Optional:
brew uninstall terminal-notifier
