Get a push notification on your phone when Claude Code needs you — an idle prompt waiting for input, or a permission request blocking a tool call.
This is opt-in. Nothing here activates unless you supply Pushover credentials.
The plugin already ships hooks/notify.sh, which raises a macOS desktop banner
via a terminal OSC escape sequence (with an osascript fallback). That is wired
through hooks/hooks.json and needs no configuration.
Phone notifications are a second, independent Notification hook. Both fire:
Desktop banner (notify.sh) |
Phone push (notify-pushover.sh) |
|
|---|---|---|
| Wired via | hooks/hooks.json (automatic) |
~/.claude/settings.json (opt-in at install) |
| Needs credentials | No | Yes — Pushover token + user key |
| Reaches you | At the machine | Anywhere |
| Cost | Free | One-time ~$5 per platform, after a 30-day trial |
You are not replacing the banner. If you never configure Pushover, nothing changes.
The obvious free alternative is ntfy.sh, which needs no account. It was tried first and rejected: on iOS its push can be deferred indefinitely under Low Power Mode, which is exactly when a long-running session is most likely to need you. Pushover is built for server→phone alerting and delivers reliably in that state.
The easiest path is the guided skill:
/tamirs-superpowers:notify-setup
It collects both credentials, validates them, wires the hook, and sends a test.
Pushover needs two 30-character credentials. Supplying only one is the most common mistake — they are not interchangeable:
| Credential | Starts with | Where to get it |
|---|---|---|
| Application/API token | a |
https://pushover.net/apps/build — register an app, name it "Claude Code" |
| User key | u |
https://pushover.net — dashboard, top right |
Then re-run the installer with both in the environment:
PUSHOVER_TOKEN=a... PUSHOVER_USER=u... bash scripts/install.shThat writes ~/.claude/pushover.env (mode 600) and appends a Notification hook to
~/.claude/settings.json. It is idempotent — re-running replaces the existing
Pushover hook rather than duplicating it, and leaves any other Notification hooks
alone.
Hooks are read at session start, so the wiring takes effect in your next session.
~/.claude/pushover.env, not the plugin directory. The marketplace cache lives at
~/.claude/plugins/cache/tamirs-marketplace/tamirs-superpowers/<version>/ and is replaced
wholesale on every plugin update — anything stored there is deleted on upgrade.
For the same reason the hook command resolves the script path at runtime:
f=$(ls "$HOME"/.claude/plugins/cache/tamirs-marketplace/tamirs-superpowers/*/scripts/notify-pushover.sh 2>/dev/null | sort -rV | head -1) && [ -n "$f" ] && bash "$f"A hardcoded version path breaks on the next update. This is the same pattern the statusline uses.
All of these go in ~/.claude/pushover.env, which is sourced alongside the credentials:
| Variable | Default | Effect |
|---|---|---|
PUSHOVER_IDLE_PRIORITY |
0 |
Priority for idle prompts |
PUSHOVER_PERMISSION_PRIORITY |
1 |
Priority for permission requests |
PUSHOVER_INCLUDE_SNIPPET |
1 |
0 sends the alert without any transcript excerpt |
PUSHOVER_RETRY / PUSHOVER_EXPIRE |
60 / 600 |
Emergency retry cadence, seconds |
PUSHOVER_DEBUG |
0 |
1 prints the API response instead of discarding it |
Pushover priority levels: -2 lowest, -1 low, 0 normal, 1 high (bypasses quiet
hours), 2 emergency (re-alerts until you acknowledge it on the device).
Permission prompts default to 1 because they block work. Idle prompts default to 0
on purpose: priority 1 bypasses Do Not Disturb, so raising it means a session going
quiet at 3am will wake you.
The title is Claude Code — <project>, where project is the basename of the session's
working directory, so parallel sessions are distinguishable at a glance.
For idle prompts the body includes up to 300 characters of Claude's last message.
That text is converted from Markdown to plain text first, by
scripts/pushover_format.py — headings, tables, code fences, links, bold, and list
markers are all flattened. Raw Markdown is unreadable in a notification, and
truncating it can leave an unterminated code fence.
With snippets enabled, that excerpt transits Pushover's servers. On a machine handling
sensitive work, set PUSHOVER_INCLUDE_SNIPPET=0 — you still get the alert and the
project name, just no conversation content.
| Symptom | Cause | Fix |
|---|---|---|
| Nothing arrives, no error at all | Unconfigured — the script exits 0 silently by design | Check ~/.claude/pushover.env exists and has both values |
"application token is invalid" |
User key pasted into the token field | Token starts a, user key starts u |
"user identifier is not a valid user" |
Token pasted into the user key field | Same swap, other direction |
| Credentials validate, phone stays silent | Pushover app not installed / signed in | Run the validate call and check the devices array is non-empty |
| Stopped working right after a plugin update | Hardcoded version path in settings.json |
Re-run the installer to restore the glob-resolving command |
Notification is a wall of ** and ## |
Missing or stale pushover_format.py |
Confirm it sits beside notify-pushover.sh in scripts/ |
| Arrives at the desk, missed when away | Idle priority too low for your setup | Set PUSHOVER_IDLE_PRIORITY=1 |
Validate credentials independently of sending:
curl -s --form-string "token=$TOKEN" --form-string "user=$USER_KEY" \
https://api.pushover.net/1/users/validate.json{"status":1,...,"devices":["iphone"]} means both credentials are good and a device
is registered. This separates two failures that look identical on the send endpoint.
Send a test through the real script:
echo '{"message":"test","notification_type":"permission_prompt","cwd":"'"$PWD"'"}' \
| PUSHOVER_DEBUG=1 bash scripts/notify-pushover.shbash scripts/uninstall.sh # unwires the hook, keeps credentials
rm ~/.claude/pushover.env # purge credentialsDeleting only the credentials file is enough: the script exits 0 silently when unconfigured, so the hook becomes a no-op.