Claude Code notifications on your phone (ntfy, Pushover, Telegram)

Updated 24 October 2026

You start a long Claude Code task, make coffee, and come back to find it sat for twenty minutes waiting on a permission prompt. A desktop notification doesn’t help when you’re not at the desk. This guide sends Claude Code’s alerts to your phone with one small hook script, names the project in each push, stays quiet while you’re at the keyboard, and covers what you can and can’t do from the phone once it buzzes.

The short answer

OptionCost and setupAnswer from the phone?
ntfy + hookFree app, no account, one curl commandNo, it only tells you
Pushover + hookOne-time in-app purchase per platform after a 30-day trialNo
Telegram bot + hookFree, create a bot with BotFatherNo
Slack or Discord webhook + hookFree, needs a workspace or serverNo
Remote Control pushClaude app, Pro, Max, Team or Enterprise loginYes, in the Claude app

If you only want to know when to walk back to your computer, use a hook. If you want to answer from wherever you are, use Remote Control. You can run both.

1. Pick the events that should reach your phone

A hook is a command Claude Code runs at a set moment, with details about the event as JSON on stdin. Three events cover “Claude is waiting on me”:

EventWhen it firesUseful fields
Notification with permission_promptClaude needs approval and you haven’t typed for about six secondsmessage, cwd
Notification with idle_promptAbout 60 seconds after Claude finishes, if you haven’t typed sincemessage, cwd
StopEvery time Claude finishes a turnlast_assistant_message, cwd
PreToolUse on AskUserQuestionClaude is about to ask you a multiple-choice questiontool_input.questions, cwd

The setup below uses permission_prompt for approvals, Stop for “done” (it carries Claude’s actual reply, which idle_prompt doesn’t), and PreToolUse for questions, since a question doesn’t count as a permission prompt. Stop fires after every turn, including the ones you watched happen, so add the away check before you rely on it.

The hook gets the project directory as cwd. Its last path segment is what goes in the title, so with three sessions running you can tell “api” from “web” at a glance.

2. Set up ntfy

ntfy turns an HTTP POST into a push notification. The public server at ntfy.sh needs no sign-up, and the apps are on Android and iOS.

  1. Install the ntfy app on your phone.
  2. Pick a topic name nobody will guess, such as claude-7f3k9q2m8x. Letters, numbers, - and _, up to 64 characters. On ntfy.sh, the topic name is effectively the password.
  3. Subscribe to that topic in the app.
  4. Send a test from your computer:
Terminalcurl -H "Title: Claude Code: test" -d "It works" https://ntfy.sh/claude-7f3k9q2m8x

If the phone buzzes, save this script as ~/.claude/hooks/push.sh. It needs jq and curl.

~/.claude/hooks/push.sh#!/bin/bash
# Claude Code hook: send a push to your phone through ntfy.
[ -f "$HOME/.config/claude-push.env" ] && . "$HOME/.config/claude-push.env"
[ -n "$NTFY_TOPIC" ] || exit 0

input=$(cat)
event=$(jq -r '.hook_event_name' <<<"$input")
project=$(basename "$(jq -r '.cwd' <<<"$input")")
priority=default

case "$event" in
  Notification)
    body=$(jq -r '.message // "Claude Code needs you"' <<<"$input")
    [ "$(jq -r '.notification_type' <<<"$input")" = permission_prompt ] && priority=high
    ;;
  Stop)
    body=$(jq -r '(.last_assistant_message // "Finished")[0:300]' <<<"$input")
    ;;
  PreToolUse)
    body=$(jq -r '.tool_input.questions[0].question // "Claude has a question"' <<<"$input")
    priority=high
    ;;
  *) exit 0 ;;
esac

auth=()
[ -n "$NTFY_TOKEN" ] && auth=(-H "Authorization: Bearer $NTFY_TOKEN")

printf '%s' "$body" | curl -fsS --max-time 10 -o /dev/null \
  -H "Title: Claude Code: $project" \
  -H "Priority: $priority" \
  "${auth[@]}" \
  --data-binary @- \
  "${NTFY_SERVER:-https://ntfy.sh}/$NTFY_TOPIC"

Make it executable with chmod +x ~/.claude/hooks/push.sh. The body goes to curl on stdin rather than as an argument on purpose: if Claude’s reply happened to start with @, -d "$body" would make curl read and upload the file of that name.

Then register it in ~/.claude/settings.json. If the file already has a "hooks" key, add these events inside it rather than adding a second one:

~/.claude/settings.json{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/push.sh" }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/push.sh" }]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "AskUserQuestion",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/push.sh" }]
      }
    ]
  }
}

Open a new session and type /hooks to check all three are listed. On ntfy, priority high means a long vibration and a pop-up, so approvals and questions stand out; finished turns use the default. Avoid low for anything you want to see: it has no sound or vibration and stays hidden until you pull down the notification drawer.

3. Keep the topic and tokens out of settings.json

The script reads its settings from ~/.config/claude-push.env, not from settings.json. That’s deliberate:

~/.config/claude-push.envNTFY_TOPIC=claude-7f3k9q2m8x
# Only for a protected or self-hosted server:
# NTFY_SERVER=https://ntfy.example.com
# NTFY_TOKEN=tk_...

Run chmod 600 ~/.config/claude-push.env so only your user can read it. With no topic set, the script exits quietly, which is also an easy way to switch pushes off.

4. Make the ntfy topic private

ntfy.sh is open by design: anyone who knows a topic name can subscribe to it and post to it. It also keeps recent messages, so someone who learns your topic later can read the backlog:

Terminalcurl -s "https://ntfy.sh/claude-7f3k9q2m8x/json?poll=1"

That matters because the Stop push contains the first 300 characters of Claude’s reply, which can include file paths, code and error output. You have three ways to tighten it:

5. Pushover, Telegram, Slack or Discord instead

Keep the script and swap the final curl command. Change the NTFY_TOPIC check near the top to test your own variable, and put the new variables in the same env file.

Pushover

Register an application on pushover.net to get an API token, and copy your user key from the dashboard. Each account can send 10,000 messages a month for free, and messages are capped at 1,024 characters.

~/.claude/hooks/push.shcurl -fsS --max-time 10 -o /dev/null \
  --form-string "token=$PUSHOVER_TOKEN" \
  --form-string "user=$PUSHOVER_USER" \
  --form-string "title=Claude Code: $project" \
  --form-string "message=$body" \
  https://api.pushover.net/1/messages.json

--form-string matters here: plain -F treats a value starting with @ or < as a file to read.

Telegram

Message @BotFather, send /newbot and copy the token. Bots can’t start a conversation, so send your new bot any message first, then find your chat ID:

Terminalcurl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates" | jq '.result[].message.chat.id'
~/.claude/hooks/push.shcurl -fsS --max-time 10 -o /dev/null \
  --data-urlencode "chat_id=$TELEGRAM_CHAT_ID" \
  --data-urlencode "text=$project: $body" \
  "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage"

Slack

Create an incoming webhook for the channel you want and copy its URL. The URL is the credential, so keep it in the env file.

~/.claude/hooks/push.shjq -n --arg text "$project: $body" '{text: $text}' |
  curl -fsS --max-time 10 -o /dev/null -H 'Content-Type: application/json' \
    --data-binary @- "$SLACK_WEBHOOK_URL"

Discord

In the channel’s settings, open Integrations → Webhooks, create one and copy its URL. Message content is limited to 2,000 characters, well above the 300 the script sends.

~/.claude/hooks/push.shjq -n --arg content "$project: $body" '{content: $content}' |
  curl -fsS --max-time 10 -o /dev/null -H 'Content-Type: application/json' \
    --data-binary @- "$DISCORD_WEBHOOK_URL"

Building the JSON with jq --arg escapes quotes and newlines in Claude’s reply. Pasting $body straight into a JSON string breaks on the first quote.

6. Only push when you’re away

Claude Code’s own notification timing only knows if you’re typing in that terminal. You might be in your browser, three feet from the screen. A better signal is how long your keyboard and mouse have been idle. Add one of these right after input=$(cat) in the script, so the hook still reads its input before it exits:

macOS# Seconds since the last key press or mouse movement
idle=$(/usr/sbin/ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {print int($NF/1000000000); exit}')
[ "${idle:-0}" -ge 120 ] || exit 0
Linux (X11)# xprintidle prints milliseconds
[ "$(xprintidle)" -ge 120000 ] || exit 0

xprintidle is an X11 tool and doesn’t report real idle time on Wayland. There, or over SSH, a marker file is simpler and works everywhere: run touch ~/.claude/away when you leave, rm ~/.claude/away when you’re back, and use this check:

Any system[ -e "$HOME/.claude/away" ] || exit 0

Two minutes is a good default. Much shorter and you get pushes while reading a long diff; much longer and a quick permission prompt waits on you for nothing.

7. Approve from your phone: Remote Control and Channels

A hook’s push is one-way. Tapping it opens ntfy or Telegram, not your session, and nothing on your computer is listening for an answer. To approve a prompt or send the next message from your phone, Claude Code has two documented options.

Remote Control

Remote Control connects the Claude app on iOS or Android, or claude.ai/code in a browser, to a session running on your machine. Your code and tools stay local.

  1. Start the session with claude --remote-control, or run /remote-control in one that’s already open. claude remote-control runs a server you drive only from other devices.
  2. On your phone, open the Claude app, tap Code and pick the session, or scan the QR code the terminal shows.
  3. For pushes, run /config and turn on Push when actions required for permission prompts and questions, Push when Claude decides for things like a finished long task, or both.

The limits are real. It needs a claude.ai login on a Pro, Max, Team or Enterprise plan; API keys aren’t supported, and on Team and Enterprise an Owner has to turn it on first. Your computer has to stay on with Claude Code running. You can’t pick Bypass permissions from the app. Claude Code also skips the push while you’re typing in or focused on that terminal; set CLAUDE_CLIENT_PRESENCE_FILE to a marker file path to skip pushes whenever that file exists.

Channels

Channels, in research preview, let you message a running session from Telegram, Discord or iMessage through official plugins, for example claude --channels plugin:telegram@claude-plugins-official. They need Bun. A channel that declares permission relay can forward approval prompts too, and anyone on the channel’s allowlist can then approve tool use in your session, so allowlist only yourself.

8. When nothing arrives

At your desk: answer from the browser with Agent Pets

Agent Pets doesn’t reach your phone and can’t be installed on one. It covers the other half of the day, when you’re at the computer but in Chrome instead of the terminal. Agent Pets turns each Claude Code, Codex, OpenCode and omp session into a pet that walks along the bottom of every page:

npx getagentpets init adds its hooks next to any you already have, including the phone hook above. Everything runs on your computer.

Get Agent Pets — was $25, now $6Get Agent Pets — $25

Questions

How do I get a Claude Code notification on my phone?

Add a Notification hook that sends a push with curl. ntfy is the quickest: install the app, subscribe to a hard-to-guess topic, and have the hook post to https://ntfy.sh/your-topic. Pushover, Telegram, Slack and Discord work the same way.

Can I approve a Claude Code permission prompt from my phone?

Not from a hook’s push; it only tells you. Start the session with claude --remote-control, or run /remote-control, and answer in the Code tab of the Claude app. It needs a Pro, Max, Team or Enterprise login and your computer has to stay on.

How do I stop getting pushes while I’m at my desk?

Have the hook check how long your keyboard and mouse have been idle and exit early if it’s under a couple of minutes. On macOS, ioreg reports HIDIdleTime; on Linux with X11, xprintidle does the same.

Is ntfy.sh private?

No. Anyone who knows a topic name can read and post to it, so treat the name like a password. For anything sensitive, self-host ntfy with deny-all access and an access token, or keep Claude’s replies out of the push.

Sources: Claude Code’s documentation on hooks, Remote Control, Claude Code on mobile and channels; ntfy’s docs on publishing, installing and server configuration; the Pushover API; the Telegram Bot API; Slack’s incoming webhooks; Discord’s webhook reference.

More guides