Urgent priority + 🚨 tag
Bash matching rm -rf, git push --force, curl … | sh, sudo, dd if=, mkfs, etc. publishes with Priority: urgent and a rotating_light tag so it cuts through Do Not Disturb on most setups.
A PermissionRequest hook that pushes pending tool calls to your phone via ntfy. Tap Allow and the harness skips the local prompt. Tap Deny and the call is rejected. Don't tap — the local terminal prompt fires as if no hook existed. Worst case it's a no-op.
Anthropic ships Claude Code Remote Control, which mirrors a CLI session to the Claude mobile app or claude.ai/code. It's great for monitoring — but the docs are explicit that interactive terminal pickers and permission prompts are local-only. If your laptop is asking "Allow this Bash call?", you have to walk back to it.
claude-ntfy-hook fills exactly that gap. It hooks into thePermissionRequestevent, posts the pending decision to your phone over ntfy, and waits for a tap. Allow runs the tool. Deny rejects it. Always runs it AND appends a conservative pattern to yourpermissions.allowso future identical calls auto-approve. No tap — the regular terminal prompt fires. Risky-looking commands (rm -rf,git push --force,curl … | sh) get urgent priority and a 🚨 tag.
Built for engineers running long Claude Code sessions and not wanting to camp the keyboard for every Write, Bash, or destructive call. ~140 lines of Python, no daemon, no server. Public ntfy.sh by default; self-hosted ntfy supported via NTFY_BASE if you'd rather not transit Heckel's servers.
Three things to set up: clone the repo, generate two random ntfy topic names, wire the hook into ~/.claude/settings.json. The included install.sh handles the first two.
install.sh symlinks the hook into ~/.claude/scripts, generates two random topic names, and writes them to .env.topics (gitignored).
git clone https://github.com/adrian-gomez/claude-ntfy-hook.git ~/code/claude-ntfy-hook cd ~/code/claude-ntfy-hook ./install.sh
Install ntfy on Android (or the iOS app), tap Subscribe, paste the NTFY_PERM_TOPIC value from .env.topics, server https://ntfy.sh. Smoke test:
source .env.topics curl -d "if you see this, ntfy works" https://ntfy.sh/$NTFY_PERM_TOPIC
Source the topics from your shell rc and merge this PermissionRequest entry into ~/.claude/settings.json alongside any other hooks you have.
echo '[ -f ~/code/claude-ntfy-hook/.env.topics ] && source ~/code/claude-ntfy-hook/.env.topics' >> ~/.zshrc
{
"hooks": {
"PermissionRequest": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 ~/.claude/scripts/ntfy_permission.py",
"timeout": 60
}
]
}
]
}
}
Open a new terminal so Claude Code inherits the env vars, then ask it to do something a notification-worthy. write "hi" to /tmp/foo works.
The hook fires only when Claude Code was about to actually prompt you (the PermissionRequest event — not for every tool call). Auto-allowed patterns and the safe-command classifier stay silent.
Hook returns {"behavior": "allow"}. Claude Code records "Allowed by PermissionRequest hook" in the trace and proceeds.
Hook returns {"behavior": "deny"}. Claude Code denies the call and surfaces the hook's reason.
Hook allows the call AND atomically appends a conservative pattern to ~/.claude/settings.json permissions.allow. Bash patterns narrow by path (rm /tmp/foo → Bash(rm /tmp/*)) or subcommand (git push origin main → Bash(git push *)); Edit/Write narrow to the parent dir. Future identical patterns won't notify. Audit log at ~/.claude/.ntfy_permission.log.
After NTFY_TIMEOUT (default 30s) the hook exits with no output. The harness falls back to the regular terminal prompt as if the hook didn't exist.
Same fall-through path. Publishing to ntfy.sh fails, hook exits 0 silent. Worst case the hook is a no-op — it never silently blocks a tool call.
Bash matching rm -rf, git push --force, curl … | sh, sudo, dd if=, mkfs, etc. publishes with Priority: urgent and a rotating_light tag so it cuts through Do Not Disturb on most setups.