Get notified when Claude Code finishes or needs your input
On this page
What happens by defaultA desktop notification from a hookWhen each notification firesThe Stop hook: the moment a turn endsPush to your phoneHow Codeman handles itClaude Code already fires a notification when it finishes or pauses for a permission prompt and you appear to be away. It shows up as a desktop notification only in Ghostty, Kitty and iTerm2 by default. Anywhere else, set preferredNotifChannel to terminal_bell, or add a Notification hook that runs any command you like, including a push to your phone.
What happens by default
From Anthropic's terminal configuration docs:
- Ghostty, Kitty and iTerm2 get a desktop notification with no setup. It even reaches your local machine over SSH. iTerm2 needs Notification Center Alerts and Send escape sequence-generated alerts turned on in its profile settings.
- Other terminals, including Warp and the VS Code integrated terminal, get nothing unless you configure it.
- Inside tmux, the notification is swallowed unless
~/.tmux.confhasset -g allow-passthrough on.
The simplest fix for other terminals is the bell, in ~/.claude/settings.json:
{
"preferredNotifChannel": "terminal_bell"
}
A desktop notification from a hook
Hooks are commands Claude Code runs at points in its lifecycle. A Notification hook runs whenever Claude Code sends a notification, whatever your notification channel is set to. On Linux:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" }
]
}
]
}
}
On macOS, replace the command with osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'. If nothing appears, macOS may not have given Script Editor notification permission; run that command once in Terminal, then allow Script Editor under System Settings > Notifications. notify-send needs a notification daemon, which SSH sessions and containers usually lack. Type /hooks in Claude Code to check what is loaded (the menu is read-only).
When each notification fires
An empty matcher fires on every notification type. To narrow it, list types separated by |:
| Matcher | Fires when |
|---|---|
permission_prompt |
Claude needs you to approve a tool use and you have not typed for about six seconds |
idle_prompt |
Claude finished responding about 60 seconds ago and you have not typed since |
elicitation_dialog |
An MCP server opened a form and you have not typed for about six seconds |
quota_auto_resume_fired |
Claude Code continued your task after a usage limit reset (v2.1.234 and later) |
The idle_prompt delay matters: a session can be finished for a full minute before you hear about it. Full list in the hooks reference.
The Stop hook: the moment a turn ends
A Stop hook runs as soon as the main agent finishes responding. It does not run when you interrupt Claude, and API errors fire StopFailure instead. Its input includes last_assistant_message, the text of Claude's final reply, so a notification can say what happened without parsing the transcript.
Two cautions. Stop fires after every turn, not only when a long task is done, so it is noisy while you are working at the keyboard. And a Stop hook that returns "decision": "block" keeps Claude working; a notification hook should print nothing and exit 0.
If you want to hear about permission prompts instantly rather than after six seconds, the PermissionRequest event runs the moment Claude asks.
Push to your phone
A desktop notification does not help when you are away from the desk. Hooks receive a JSON object on stdin with cwd, and for notifications message, title and notification_type, so one script can forward either event. This one uses ntfy, which needs no account:
#!/usr/bin/env bash
# ~/.claude/hooks/push.sh: forward a Claude Code hook event to your phone via ntfy.
topic='pick-a-long-random-topic-name' # anyone who knows it can read your pushes
input=$(cat)
project=$(basename "$(jq -r '.cwd // "."' <<<"$input")")
title=$(jq -r '.title // "Claude Code"' <<<"$input")
msg=$(jq -r '.message // .last_assistant_message // "Finished"' <<<"$input" | head -c 300)
curl -s -H "Title: $title ($project)" -d "$msg" "https://ntfy.sh/$topic" >/dev/null
{
"hooks": {
"Notification": [
{ "matcher": "permission_prompt|idle_prompt",
"hooks": [{ "type": "command", "command": "$HOME/.claude/hooks/push.sh" }] }
]
}
}
Make the script executable (chmod +x), install the ntfy app on your phone and subscribe to the same topic. The ntfy docs say it plainly: the topic is essentially a password. Messages pass through the ntfy.sh server, so if Claude's replies might contain anything sensitive, send only the project name or run your own ntfy server. Add the same command under Stop if you want a push after every turn.
How Codeman handles it
Codeman runs Claude Code and other agent CLIs in tmux sessions behind a browser dashboard, and writes Claude Code hooks into each project's .claude/settings.local.json so the CLI reports permission prompts, idle prompts and finished turns back to it. Codeman only updates the hook block it wrote, so your own hooks, like the ones above, keep working next to it.
What you get from those events:
- Tab alerts. A tab blinks yellow when the agent waits for input and red when a question or permission prompt blocks it. The state survives a page reload.
- Web push (opt-in, in App Settings → Notifications). Reaches your phone with no tab open. It needs HTTPS, and on iOS Codeman must be added to the home screen first.
- Approvals Inbox (opt-in). Every prompt waiting on a human, across all sessions, in one list. With it on, push notifications carry Approve and Deny buttons that work from a locked phone. Before sending a menu answer, Codeman re-reads the screen and refuses if the dialog is gone, so a stale tap cannot type a digit into the input box.
- No alert for self-inflicted waits. A session that ended its turn to wait on its own monitor or background shell gets a "watching" badge instead of an alert.
- Window title prefix. Browser titles read
codeman:<host>, so tabs from several machines stay distinguishable.
These precise signals exist for Claude Code sessions, and DeepSeek Harness sessions report the same states themselves. Other CLIs have no hooks, so Codeman falls back to watching terminal output, which is coarser. See Notifications And Approvals and Hooks And Integrations.