GuidesHosting choices

Claude Code notifications: on your phone, even from a server

When Claude Code needs you or finishes: its terminal alerts, pushes to the Claude app, and a Notification hook that reaches your phone from a headless server.

October 13, 2026The Everpod team
The short answer

Claude Code tells you it needs you in three ways. It sends a terminal alert, a desktop notification in iTerm2, Ghostty or Kitty, which reaches your laptop over SSH but only while your terminal is attached and showing the session. It can push to the Claude app on your phone, through two /config switches, while Remote Control is connected. And it runs a Notification hook when a permission prompt has waited about six seconds or a finished reply has sat for 60, which can call ntfy, Telegram, Slack or anything else with a URL. On a server you are not watching, the hook is the one that always gets through. In a claude -p run the Notification event does not fire (Claude Code 2.1.296); use the Stop hook, which carries Claude’s final reply.

Which alert reaches you

Reaches you whenNeeds
Terminal alertYour terminal is open on the session, including over SSHiTerm2, Ghostty or Kitty, or the bell setting; tmux passthrough on
Push through the Claude appRemote Control is connected and you are not at the terminalA claude.ai sign-in (Pro, Max, Team or Enterprise); no API key set
Notification or Stop hookAlways: it is a command the machine runsA service to post to, such as ntfy or a Telegram bot

The terminal alert, and why a server swallows it

Anthropic’s terminal setup page describes the built-in alert: when Claude finishes or waits on a permission prompt “and you appear to be away from the terminal, it fires a notification event.” The setting that decides what that event does is preferredNotifChannel, shown in /config as Local notifications. Its default, "auto", in the settings reference’s words, “sends a desktop notification in iTerm2, Ghostty, and Kitty, rings the bell in Terminal.app only when its audible bell is off, and does nothing elsewhere.” The other values are terminal_bell (the bell character in any terminal), iterm2, iterm2_with_bell, kitty, ghostty and notifications_disabled. In Warp, VS Code’s terminal and most others, nothing happens unless you set "preferredNotifChannel": "terminal_bell" in ~/.claude/settings.json.

The alert is an escape sequence written to the terminal, so “The desktop notification reaches your local machine over SSH, so a remote session can still alert you.” iTerm2 has to be told to pass it on (Settings, Profiles, Terminal: Notification Center Alerts, then Filter Alerts and “Send escape sequence-generated alerts”). Inside tmux it is swallowed until you add set -g allow-passthrough on to ~/.tmux.conf, and tmux’s manual adds the catch: with on, “passthrough sequences will be allowed only if the pane is visible.” That is the limit of every terminal alert. When you close the laptop and the session carries on in a detached tmux on the server, there is no terminal for the sequence to reach, and the Linux example in Anthropic’s own hooks guide, notify-send, “needs a desktop notification daemon, which headless servers, SSH sessions, and most containers don’t have.”

Pushes through the Claude app

With Remote Control connected, Claude Code can push to the Claude app on your phone. Two settings switch it on, both off by default and both settable in /config or ~/.claude/settings.json:

The conditions are Remote Control’s. Its page says it is “available on Pro, Max, Team, and Enterprise plans. API keys are not supported”, and since version 2.1.139 (May 2026) “Remote Control, /schedule, claude.ai MCP connectors, and notification preferences are now disabled when ANTHROPIC_API_KEY / apiKeyHelper / ANTHROPIC_AUTH_TOKEN is set, even if a Claude.ai login also exists.” A server with an API key exported in its shell gets no pushes, whatever /config says. Pushes are also skipped while you are typing in or focused on the connected terminal, and the session has to be running: on a server, start it in tmux.

A hook that reaches your phone from anywhere

A hook is a command Claude Code runs at a fixed point, with the event as JSON on standard input (the hooks guide covers how they work). The hooks reference says Notification hooks “are intended for side effects such as forwarding the notification to an external service”, and they fire whether or not a terminal is attached. Three events matter here, shown with what Claude Code 2.1.296 passed on October 13, 2026:

EventFiresIts message
Notification, type permission_promptA permission prompt has waited about six seconds with no keystroke“Claude needs your permission”
Notification, type idle_promptAbout 60 seconds after Claude finishes, if you have not typed“Claude is waiting for your input”
StopEvery time Claude finishes responding, but not when you interrupt itThe reply itself, in last_assistant_message

In an interactive session left in a detached tmux on a server, with nobody attached, the idle_prompt notification fired exactly 60 seconds after the reply ended, and the permission one fired while the prompt waited. Neither payload carried the optional title field the reference’s example shows, so a hook should not rely on it. Declining a prompt with Escape ends the turn with no Stop and no idle notification after it.

The lightest place to send them is ntfy: install its app on your phone, subscribe to a topic, and anything posted to that topic arrives as a notification. There is no account, so in ntfy’s words “the topic is essentially a password”: make it long and random. Save this as ~/.claude/hooks/notify-phone.sh and make it executable:

#!/usr/bin/env bash
# Claude Code hook: send the notification, or the final reply, to your phone via ntfy.
# Reads the hook's JSON on stdin. Needs jq and curl.
topic="replace-with-a-long-random-name"
input=$(cat)
msg=$(jq -r '.message // .last_assistant_message // "Claude Code needs you"' <<<"$input" | head -c 3000)
curl -s --max-time 10 -H "Title: Claude Code on $(hostname)" --data-raw "$msg" "https://ntfy.sh/$topic" >/dev/null
exit 0

Then register it in ~/.claude/settings.json, which applies to every project on the machine:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt|idle_prompt",
        "hooks": [{ "type": "command", "command": "$HOME/.claude/hooks/notify-phone.sh" }]
      }
    ]
  }
}

Run on a cloud server with a captured permission_prompt payload, the script exited in under a third of a second, and the topic held one message titled “Claude Code on” the server’s name, reading “Claude needs your permission”. A few details in it are deliberate: --data-raw sends the text exactly as it is, where curl’s -d and --data-binary would read a message starting with @ as a file name and post that file instead (a two-line reply starting with @/etc/hostname arrived on the topic word for word); head -c 3000 keeps it under ntfy’s 4,096-byte message limit; and --max-time stops a slow network from holding the hook. The free ntfy.sh service allows 250 messages a day, and you can run ntfy’s server yourself and point the app at it.

To hear when a long task is done rather than only when Claude is waiting, add the same script under Stop, which sends the reply itself. That puts Claude’s whole answer, code and anything it printed included, on a service whose only lock is the topic name, so run your own ntfy server for that, or send only a fixed line. It also fires after every reply, so in a chatty session it is noisy; the idle_prompt notification, which waits a minute and only fires if you have not typed, is the quieter signal that a turn ended while you were away.

Telegram, Slack or email instead

ProviderBlocked by defaultLeft open for a mail service
Hetzner Cloud25, 465587
DigitalOcean25, 465, 587None: it recommends a third-party email service
AWS EC225, to public addressesIts restriction names port 25 only
Google Cloud25, outside your network465, 587
Azure25, except on Enterprise Agreement subscriptions587
Vultr25465, 587
Akamai (Linode)25, 465, 587 on some accounts made since November 2019Depends on the account

ntfy can also forward a message as email (?email= on the topic URL) once you verify an address in its web app, five a day on the free service.

When nobody is at the keyboard: claude -p

A script, a cron job or a systemd timer runs Claude Code in print mode, and there the Notification event does not come. With Claude Code 2.1.296, a claude -p run fired Stop and then SessionEnd, and no Notification, even when it reached for a command that needed approval: the call was refused, its JSON result listed it under permission_denials, and a PermissionRequest hook, if you have one, is what fires at that moment. So for a run nobody is watching:

The guide to scheduling Claude Code covers which schedulers keep running with your laptop closed. A print-mode run with nothing on its standard input also waits three seconds and warns “no stdin data received” before going on; ending its command with < /dev/null skips that.

Codex

Codex’s terminal interface notifies by itself: tui.notifications is on by default, tui.notification_method is auto, osc9 or bel, and tui.notification_condition defaults to firing only when the terminal is unfocused, which raises the same problem as Claude Code’s on a server you are not watching. For anything else it has notify, set in ~/.codex/config.toml, which per OpenAI’s advanced configuration page runs a program “whenever Codex emits supported events (currently only agent-turn-complete)”, passing a JSON argument with the last-assistant-message:

# ~/.codex/config.toml
notify = ["/home/you/.codex/notify-phone.sh"]

# ~/.codex/notify-phone.sh: the JSON is the last argument, not standard input
msg=$(jq -r '.["last-assistant-message"] // "Codex finished a turn"' <<<"${@: -1}" | head -c 3000)
curl -s --max-time 10 -H "Title: Codex on $(hostname)" --data-raw "$msg" "https://ntfy.sh/replace-with-a-long-random-name" >/dev/null

Three things to know about it. Codex ignores notify in a project’s own .codex/config.toml, so it has to be in your user config. The JSON arrives as the last command-line argument, not on standard input, and its field is last-assistant-message, so the Claude Code script above does not work here unchanged; the one in the block reads the argument instead. And in Codex’s source the program’s output is discarded and its exit not waited for, so a script that fails, fails silently: test it once by hand. Codex’s hooks have a Stop event with last_assistant_message too, though unlike Claude Code it “expects JSON on stdout when it exits 0”, so a notifier there should print nothing. Codex’s own phone route starts from the ChatGPT desktop app on a Mac or a Windows machine, as the Codex remote control guide explains, so a Codex CLI alone on a Linux server reaches your phone through notify or a hook.

Run Claude Code on an always-on developer pod.

A developer pod is a cloud computer of your own with your pick of Claude Code, Codex, OpenCode, Pi, Hermes and OpenClaw installed, reached only over your own Tailscale network. From $24 a month, built in about ten minutes.