To get Claude Code to push to your phone through ntfy, install the ntfy app, run npx anotifier@latest setup, answer yes to phone notifications, and subscribe to the topic it prints. The same topic also carries Codex, Cursor and Gemini CLI. Prefer no tool at all? A Stop hook that runs curl against your topic does the same for Claude Code alone. ntfy is a free, open-source push service: anything posted to a topic arrives in the app, and there is no account to create. For the hook side in more depth, see the Claude Code notifications guide.
Rather skip the manual setup? One command wires every agent you have installed to desktop toasts, phone push, and Slack, Discord or Telegram:
Updated 2026-10-16 · also see the full docs
npx anotifier@latest setup and answer yes to Enable phone notifications via ntfy? Keep the default server, https://ntfy.sh, and the generated topic: anotifier- followed by 16 random letters and digits. You can type your own topic instead, as long as it is long and hard to guess.anotifier test ntfy. Your phone should buzz within a second.Setup writes the choice to ~/.anotifier/config.json. You can also edit it yourself, or run anotifier config ntfy to change the server, topic, icon and click URL:
{
"ntfy": {
"enabled": true,
"server": "https://ntfy.sh",
"topic": "anotifier-k3j9x0w7p2m8q4zt"
}
}Claude Code runs hooks from ~/.claude/settings.json. The Stop hook fires when Claude finishes responding, and the Notification hook fires when it needs you; its matcher is the notification type, such as permission_prompt or idle_prompt. Each hook can run curl against your topic:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -o /dev/null -H 'Title: Claude Code' -d 'Task complete' https://ntfy.sh/your-long-random-topic"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "curl -s -o /dev/null -H 'Title: Claude Code' -H 'Priority: urgent' -H 'Tags: warning' -d 'Needs your permission' https://ntfy.sh/your-long-random-topic"
}
]
}
]
}
}your-long-random-topic with a name nobody could guess, and subscribe to it in the app.-s -o /dev/null keeps ntfy's JSON reply off the hook's output. That suits a Unix shell; in Windows PowerShell 5.1 run curl.exe, because curl is an alias for Invoke-WebRequest there.Notification entry per type you care about. Without a matcher, the hook runs for every notification type, including ones that wait on nothing, such as a finished login.That is the whole mechanism. anotifier adds the project name to the title, a sensible priority per event, a quieter idle reminder, snooze and quiet hours, one setup for every agent, and a fallback server.
| Event | ntfy priority | Tags |
|---|---|---|
| Task finished | default | white_check_mark |
| Approval or question | urgent | bell, warning |
| Claude Code idle reminder | default | hourglass_flowing_sand |
| Claude Code notice that waits on nothing, such as a finished background agent | default | information_source |
| Session start | not sent | None |
The title is my-app · Claude Code, sent encoded so accents and emoji in project names survive. The body is generic text such as my-app: Task complete: anotifier keeps conversation text out of ntfy by default (ntfy.richContent is false), because anyone who knows a public topic name can read it.
ntfy has five priorities, set with the Priority header (aliases X-Priority, prio and p): 5 or max/urgent, 4 high, 3 default, 2 low and 1 min. Tags go in the Tags header (alias X-Tags); a tag that matches an emoji short code, like warning, is shown as that emoji.
In anotifier both are per event in ~/.anotifier/config.json. priority takes min, low, default, high or urgent, ntfyTags is a comma-separated list, and ntfyEnabled: false turns the phone push off for one event:
{
"events": {
"task_complete": { "priority": "low", "ntfyTags": "tada" },
"needs_input": { "priority": "high", "idleReminderPriority": "min" },
"session_start": { "ntfyEnabled": true }
}
}idleReminderPriority sets the level of Claude Code's idle reminder, which is default unless you change it.
claude.anotifier status prints the full ntfy URL.richContent for it (below).ntfy can also protect a topic with a username and password or an access token (Authorization: Bearer tk_…). anotifier (as of 1.7.1) has no config key for either: it never sends an Authorization header to ntfy, so the server has to accept anonymous publishing to your topic. The pattern that keeps your messages private on your own server is to deny anonymous reads and allow anonymous writes to that one topic (server.yml, then the ntfy command), and to sign in to the server in the ntfy app as that user so your phone can read:
# server.yml
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
# then, on the server
ntfy user add phone
ntfy access phone anotifier-your-topic read-only
ntfy access everyone anotifier-your-topic write-onlyAnyone who learns the topic name can then post to it, but cannot read it. The by-hand hook above has no such limit: add -H 'Authorization: Bearer tk_…' to its curl and it publishes to a protected topic.
{
"ntfy": {
"enabled": true,
"server": "https://ntfy.example.com",
"fallbackServer": "https://ntfy.sh",
"topic": "anotifier-your-topic",
"click": "",
"richContent": true
}
}ntfy.example.com is logged as an invalid URL.fallbackServer (1.7.0 and later) is a second server for the same topic. If server is unreachable, times out or answers with an error such as a 429 limit, the same push is sent there once. Subscribe your phone to the topic on both servers. A fallback shortens each attempt from 5 to 3 seconds, and if the main server accepts a push but answers slowly, the fallback sends it too, so a phone subscribed on both can buzz twice.click is an optional URL to open when you tap the notification.anotifier config ntfy edits the server, topic, icon, and click URL interactively. fallbackServer and richContent are only set in ~/.anotifier/config.json.upstream-base-url: "https://ntfy.sh" in its server.yml, or notifications can take hours to arrive. Only a poll request with a message ID goes upstream, not the message.ntfy serve, or the binwiederhier/ntfy Docker image.curl -H 'Title: Test' -H 'Priority: urgent' -H 'Tags: warning' -d 'Hello from curl' https://ntfy.sh/your-long-random-topicIf that arrives, the app and topic are right and any remaining problem is in the hook. Then check anotifier itself: anotifier test ntfy sends a test push through your real config and prints ntfy sent, ntfy failed or ntfy not configured. If only the fallback delivered it, the output says so. anotifier test ignores snooze and quiet hours, so it works while notifications are paused. In Windows PowerShell 5.1, run the curl line as curl.exe.
anotifier test ntfy prints ntfy not configured if ntfy was skipped during setup: run setup again, or anotifier config ntfy.ntfy failed: check the server URL, then look in ~/.anotifier/errors.log for ntfy server responded <code>, ntfy server is not a valid URL or a timeout (anotifier gives ntfy 5 seconds, or 3 with a fallback server).anotifier status prints the exact server and topic anotifier posts to; compare it character by character with the subscription in the app.New message: on a self-hosted server, set upstream-base-url as above. ntfy's docs say that without it notifications can take hours, and New message usually means the iPhone cannot reach your server. ntfy also lists a known iOS issue where the list does not refresh until you swipe down.anotifier snooze and quiet hours silence every channel, ntfy included. anotifier status shows the state.fallbackServer, or run your own server, whose limits you choose.Install the ntfy app, run npx anotifier@latest setup and answer yes to phone notifications, then subscribe to the topic it prints. Or add a Stop hook to ~/.claude/settings.json that runs curl -d 'Task complete' against https://ntfy.sh/your-topic.
ntfy is open source, the public ntfy.sh server is free to use, and you can run your own server instead. You subscribe to a topic name in the app; there is nothing to sign up for.
Yes. Setup wires every agent it finds to the same ntfy topic, so all of them land in one stream. Their alerts carry generic text, and the title names the project where the agent reports one.
Not in anotifier (as of 1.7.1), which sends no Authorization header to ntfy. Use a server that allows anonymous writes to your topic and denies anonymous reads. Your own curl hook can send a Bearer token.
Yes. Set ntfy.server in ~/.anotifier/config.json, and optionally ntfy.fallbackServer, which is tried once if the main server fails. On iOS, set upstream-base-url on your server.
Yes. Enter the same topic during setup on each machine. Every notification title names the project, so you can tell runs apart.
Not with anotifier (as of 1.7.1). The urgent push gets you back to the keyboard fast; the decision itself stays in the terminal.
One command wires every agent you have installed: Claude Code, Codex CLI, Cursor, and Gemini CLI.
npx anotifier@latest setup