You step away from your desk to grab a coffee. When you come back, Claude Code has been waiting for your input for twelve minutes. The task is stalled and you just burned a chunk of your session staring at a coffee machine.

If you use Claude Code daily, you know this feeling.

The problem

Claude Code runs in a terminal. When it needs your input, it waits. Silently.

Your terminal emulator (iTerm2, Ghostty, etc.) might show a system notification if you’re at your desk. But what about when you’re on another Mac across the room, on your phone on the couch, making lunch, or in a meeting with your laptop closed?

In all those cases, you have no idea Claude Code is waiting for you. Minutes pass. Sometimes half an hour. You wanted to delegate work and do something else, but that only works if you know when the agent needs you back.

What I wanted was simple: a push notification on my phone whenever Claude Code needs my attention, with a way to toggle it on and off from the command line.

Possible solutions

I looked at four approaches.

1. Pushover

Pushover is a dedicated push notification service with native iOS and Android apps, a simple HTTP API, and support for priorities, sounds, and delivery receipts.

Upside: reliable, feature-rich, native apps.
Downside: costs $5 per platform, requires an API key and a user key. Overkill for a single notification use case.

2. Slack incoming webhooks

If you already live in Slack, you can create an incoming webhook and send notifications to a channel or DM.

Upside: no extra app if you already use Slack.
Downside: requires creating a Slack app, managing a webhook URL, and the notifications blend in with everything else. Too much setup for a personal tool.

3. Telegram bot

Create a Telegram bot, get your chat ID, and POST messages via the Bot API.

Upside: free, Telegram is fast.
Downside: requires creating a bot through BotFather, storing a bot token, fetching your chat ID. More moving parts than necessary.

4. ntfy.sh

ntfy.sh is an open-source HTTP-based pub-sub notification service. You publish a message with a single curl call. You subscribe on your phone by entering a topic name. That’s it.

Upside:

  • Free, and self-hostable
  • No account, no API key, no authentication
  • Native apps for iOS and Android
  • One curl command sends a notification
  • Open source

Downside:

  • Topics are public. Anyone who knows your topic name can subscribe. Fine for non-sensitive “hey, Claude needs you” pings.

Why ntfy.sh

ntfy.sh won because it has zero setup friction. No accounts to create, no tokens to store. The entire “send a notification” logic is one curl command. For a personal tool that sends non-sensitive pings, the public topic trade-off is fine.

The implementation

The solution has four parts: a toggle script to turn notifications on or off, a hook script that sends the push via ntfy.sh, a Claude Code hooks configuration that triggers the hook, and a shell alias for convenience.

How it fits together

claude-notify on
    -> creates ~/.claude-notify-enabled (flag file)

Claude Code needs input
    -> triggers Notification hook
    -> runs notify-hook.sh
    -> checks if flag file exists
    -> if yes: curl -> ntfy.sh -> push to all subscribed devices
    -> if no: exit silently

The flag file approach means there is no daemon, no background process, no state to manage. A file exists or it doesn’t.

1. The toggle script

File: ~/.claude/claude-notify.sh

#!/bin/bash
# Toggle notifiche push per Claude Code via ntfy.sh

FLAG_FILE="$HOME/.claude-notify-enabled"

case "${1}" in
  on)
    touch "$FLAG_FILE"
    echo "Notifiche push Claude Code: ATTIVE"
    ;;
  off)
    rm -f "$FLAG_FILE"
    echo "Notifiche push Claude Code: DISATTIVATE"
    ;;
  status)
    if [[ -f "$FLAG_FILE" ]]; then
      echo "Notifiche push Claude Code: ATTIVE"
    else
      echo "Notifiche push Claude Code: DISATTIVATE"
    fi
    ;;
  *)
    echo "Uso: claude-notify {on|off|status}"
    exit 1
    ;;
esac

Three operations: on creates an empty flag file at ~/.claude-notify-enabled, off removes it, status checks if it exists. No config files to parse, no environment variables. The presence or absence of a file is the state.

2. The notification hook

File: ~/.claude/notify-hook.sh

#!/bin/bash
# Hook notification per Claude Code — invia push via ntfy.sh

FLAG_FILE="$HOME/.claude-notify-enabled"
NTFY_TOPIC="gianx-claude-mac-work"

[[ ! -f "$FLAG_FILE" ]] && exit 0

curl -s 
  -H "Title: Claude Code" 
  -H "Tags: robot" 
  -d "${CLAUDE_NOTIFICATION:-Claude Code richiede la tua attenzione}" 
  "https://ntfy.sh/${NTFY_TOPIC}" > /dev/null 2>&1

If the flag file doesn’t exist, the script exits with code 0. Claude Code won’t see any errors; the hook just does nothing.

The curl call is straightforward: -s silences progress output, the Title header sets the notification title, Tags adds a robot emoji. The body comes from $CLAUDE_NOTIFICATION, an environment variable that Claude Code sets automatically with the notification content. If the variable is empty, a default Italian message is used as fallback.

NTFY_TOPIC is your personal topic name. Change it to something unique to you, since anyone who knows the topic can subscribe.

3. Claude Code hooks configuration

File: ~/.claude/settings.json

Add the hooks section to your existing settings:

{
  "permissions": {
    "allow": [
      "WebFetch",
      "Bash(curl *)",
      "Read"
    ]
  },
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/notify-hook.sh"
          }
        ]
      }
    ]
  }
}

Claude Code’s hook system supports several event types. The Notification event fires whenever Claude Code would normally show a system notification, typically when it’s waiting for user input after completing a step.

Each event type contains an array of hook groups, and each group has a hooks array with the actual commands. type: "command" tells Claude Code to run a shell command.

4. The shell alias

Add to ~/.zshrc:

alias claude-notify='bash ~/.claude/claude-notify.sh'

Then restart your terminal or run source ~/.zshrc.

Setting up the receiving end

On every device where you want notifications:

  1. Install the ntfy app (iOS, Android)
  2. Open the app and subscribe to a topic
  3. Enter your topic name (e.g., gianx-claude-mac-work)

Notifications will show up like regular push notifications. You can also subscribe via the web at ntfy.sh/your-topic-name, or use the ntfy desktop app on another Mac.

Usage

A typical session:

# Start of your work session
claude-notify on
# -> Notifiche push Claude Code: ATTIVE

# Check status
claude-notify status
# -> Notifiche push Claude Code: ATTIVE

# Work normally
claude

# Whenever Claude Code needs your input, your phone buzzes

# End of session (or just leave them on)
claude-notify off
# -> Notifiche push Claude Code: DISATTIVATE

Verification

To test the whole chain:

# 1. Enable notifications
claude-notify on

# 2. Verify the flag file exists
ls -la ~/.claude-notify-enabled

# 3. Test the hook manually
CLAUDE_NOTIFICATION="Test notification from CLI" bash ~/.claude/notify-hook.sh

# 4. Check your phone, you should see the notification

# 5. Disable and verify it stops
claude-notify off
CLAUDE_NOTIFICATION="This should NOT arrive" bash ~/.claude/notify-hook.sh
# -> Nothing on your phone. Good.

Conclusion

Claude Code notifications only reach you if you’re sitting at your terminal. This setup uses ntfy.sh as a push relay, wired into Claude Code through its hooks system, so notifications follow you to any device.

The trade-off is that ntfy.sh topics are public, but for “Claude needs you” pings that contain no sensitive data, I’m fine with that.

Four files, no dependencies beyond curl, no running services. It turns on and off with one command.

Comment on Fediverse (Mastodon)