How I Built a Mobile Approval System for Claude Code So I Can Finally Leave My Desk
Source: Dev.to
The Problem Every AI‑Coding‑Agent User Faces
I came back with my coffee and found Claude Code frozen for 15 minutes.
As AI coding agents gain autonomy — writing code, running builds, modifying files — human oversight becomes critical. You want the agent to keep working, but you also want to know what it’s doing. The permission prompt is the checkpoint, but it’s also the bottleneck.
- Adding commands to the allowlist reduces prompts, but blanket‑approving unknown commands and file writes is risky.
- Staying glued to the terminal isn’t realistic either.
So I built claude‑push — an async human‑in‑the‑loop approval layer for Claude Code. It uses PermissionRequest hooks and ntfy.sh to send Allow/Deny push notifications straight to your phone. Open source, 3‑minute setup.
The Dilemma: Stay at Your Desk or Allow Everything
When you delegate code generation and refactoring to Claude Code, you’ll inevitably see prompts like this:
Claude wants to run: rm -rf dist && npm run build
Allow? (y/n)
Every time this pops up you have to go back to the terminal and hit y. If you’re deep in focus (or just grabbing coffee) you miss it and Claude Code sits idle.
| Approach | Pros | Cons |
|---|---|---|
| Manual approval in terminal | Safe | Can’t leave your desk |
| Allow‑list everything | Convenient | Unknown commands get through |
| Mobile push notifications | Away from desk + safe | Requires initial setup |
claude‑push makes option 3 a reality.
Prior Art
konsti‑web/claude_push tackled the same problem, but it relies on Windows + PowerShell + keystroke injection — it doesn’t work on macOS or Linux. I had the same pain point, so I rebuilt the concept from scratch using Bash + PermissionRequest hooks.
How It Works: PermissionRequest Hook + ntfy.sh
Claude Code has a Hooks system that lets you run external scripts on specific events. By registering a hook on the PermissionRequest event, you can intercept the permission prompt and return your own decision.
Claude Code requests permission
→ Hook script fires
→ Sends notification with Allow/Deny buttons to ntfy.sh
→ Phone receives the push notification
→ You tap Allow or Deny
→ Response received via ntfy.sh SSE
→ Hook returns allow/deny JSON
→ Claude Code continues or stops
ntfy.sh is a free, HTTP‑based push‑notification service. No account is required — if you know the topic name, you can send and receive notifications. Unlike Pushover or LINE Notify, you can send a notification with a single curl command, which makes it a perfect fit for hook scripts.
Implementation Details
The hook script is about 60 lines of Bash. Below are the three key design decisions.
1. Using ntfy.sh HTTP Actions for Buttons
ntfy.sh supports Action Buttons. I use http actions so that tapping a button POSTs to a separate response topic.
curl -s -H "Content-Type: application/json" \
-d "$(jq -n \
--arg topic "$TOPIC" \
--arg title "[$PROJECT] $TOOL_NAME" \
--arg message "$TOOL_INPUT" \
--arg allow_url "https://ntfy.sh/${RESPONSE_TOPIC}" \
--arg allow_body "allow|$REQ_ID" \
--arg deny_url "https://ntfy.sh/${RESPONSE_TOPIC}" \
--arg deny_body "deny|$REQ_ID" \
'{
topic: $topic,
title: $title,
message: $message,
priority: 4,
tags: ["lock"],
actions: [
{action:"http", label:"Allow", url:$allow_url, method:"POST", body:$allow_body},
{action:"http", label:"Deny", url:$deny_url, method:"POST", body:$deny_body}
]
}')" "https://ntfy.sh/"
Key detail: the notification topic and the response topic are separate channels. This prevents the SSE stream from being polluted by your own outgoing notifications.
2. SSE + Request ID for Response Matching
After sending the notification, the hook waits for a response on the ntfy.sh SSE endpoint.
REQ_ID="$(date +%s)-$$"
while IFS= read -r line; do
if [[ "$line" == data:* ]]; then
DATA="${line#data: }"
MSG=$(echo "$DATA" | jq -r '.message // empty' 2>/dev/null)
if [[ "$MSG" == *"|$REQ_ID" ]]; then
DECISION="${MSG%%|*}"
break
fi
fi
done < <(curl -s -N --max-time "$WAIT_TIMEOUT" \
-H "Accept: text/event-stream" \
"https://ntfy.sh/${RESPONSE_TOPIC}/sse")
The REQ_ID (timestamp + PID) is embedded in the notification body and matched against the response. This ensures that even when multiple permission requests fire simultaneously, each response is matched to the correct request. Without this, a previous notification’s response could be applied to a new request.
3. Timeout Fallback to Terminal
If no response arrives within the timeout window, the script outputs nothing and exits with code 0.
if [ "$DECISION" = "allow" ]; then
jq -n '{hookSpecificOutput:{decision:{behavior:"allow"}}}'
elif [ "$DECISION" = "deny" ]; then
jq -n '{hookSpecificOutput:{decision:{behavior:"deny"}}}'
fi
# Timeout: no output → falls back to interactive prompt
In Claude Code’s hook specification, no output + exit 0 means “the hook didn’t make a decision,” which falls back to the standard terminal prompt.
Quick Start (≈ 3 minutes)
- Create an ntfy.sh topic (e.g.,
myproject-perm). - Add the hook script to
~/.config/claude-code/hooks/PermissionRequest.sh(make it executable). - Set environment variables (or edit the script) for:
TOPIC– the notification topic you created.RESPONSE_TOPIC– a second topic for responses (e.g.,myproject-perm-resp).WAIT_TIMEOUT– seconds to wait for a response (default 30).
- Restart Claude Code – the next permission request will trigger a push notification to your phone.
That’s it! You can now approve or deny Claude Code actions from anywhere, without keeping your terminal in view. 🎉
Claude‑Push – Mobile Approval for Claude Code
Even if you’re not looking at your phone, you can still handle approvals from the terminal after the timeout. No permissions are silently granted.
Setup
Prerequisites
- macOS or Linux
bash,jq,curlinstalled- ntfy app installed on your phone (ntfy.sh)
Installation
git clone https://github.com/coa00/claude-push.git
cd claude-push
bash install.sh
The installer walks you through the following steps:
| Step | What it does |
|---|---|
| Dependency check | Verifies jq and curl are available |
| Topic name input | Generates a random one if left blank (e.g. claude-push-a1b2c3d4) |
| Config file creation | Writes to ~/.config/claude-push/config |
| Hook deployment | Places script at ~/.local/share/claude-push/hooks/claude-push.sh |
| Claude settings registration | Safely merges the hook into ~/.claude/settings.json using jq |
| Test notification | Sends a test push to verify everything works |
After installation, subscribe to your topic in the ntfy app and you’re ready to go.
Configuration
Edit ~/.config/claude-push/config. No re‑installation is needed.
# Topic name (acts as a shared secret for your ntfy.sh channel)
CLAUDE_PUSH_TOPIC="my-unique-topic"
# Timeout in seconds (falls back to terminal prompt after this)
CLAUDE_PUSH_TIMEOUT=90
Verification
# Send a test notification with Allow/Deny buttons
bash scripts/test.sh test-notify
# Check installation status
bash scripts/test.sh status
Sample output of status:
=== claude-push status ===
[OK] Config: ~/.config/claude-push/config
Topic: my-unique-topic
Timeout: 90s
[OK] Hook: ~/.local/share/claude-push/hooks/claude-push.sh
[OK] Settings: hook registered in ~/.claude/settings.json
[OK] Dependency: jq
[OK] Dependency: curl
Uninstall
bash uninstall.sh
The script removes the hook from Claude settings and cleans up all installed files.
What It Looks Like in Practice
-
Ask Claude Code to refactor something, then walk away from your desk.
-
A few minutes later your phone buzzes:
[myproject] Bash: npm run build -
Tap Allow on the phone.
-
When you return, the build is already finished.
You can approve or deny commands while you’re in meetings, on a walk, or getting coffee—provided you have your phone.
Before / After Comparison
| Feature | Before (terminal only) | After (mobile push) |
|---|---|---|
| Approval method | y / n in terminal | Allow / Deny buttons on phone |
| While away | Claude stops and waits | Handle via push notification |
| Timeout | None (waits forever) | Falls back to terminal after 90 s |
| Security | Allowlist or manual check | Case‑by‑case approval per notification |
| Setup cost | — | bash install.sh in ~3 min |
Security Considerations
- The ntfy.sh topic name is a shared secret. Anyone who knows it can send notifications or forge responses.
- Use a random, hard‑to‑guess string for the topic name (the installer generates one by default).
- For stricter control, configure ntfy.sh access control.
- Keep dangerous commands (e.g.,
rm -rf …) explicitly blocked in your allowlist as an extra safety layer.
Wrapping Up
Claude Code’s original permission system forced you to either babysit the terminal or whitelist everything. claude-push adds a third option – mobile approval.
- Implementation: Bash + ntfy.sh HTTP actions + Server‑Sent Events.
- The hook itself is only ~60 lines.
- It works because Claude Code’s Hooks API simply expects a JSON object with
allowordeny.
The pattern applies to any AI coding agent: as agents become more capable, we need lightweight, asynchronous approval mechanisms that don’t require us to stare at a screen. Mobile push notifications hit that sweet spot—fast enough to keep the agent moving, visible enough for oversight.
The best developer tool is the one that lets you stop staring at a screen for five minutes. claude-push is that tool.
How do you handle AI‑agent permissions? Whether you’re an allow‑list fan or a manual‑check advocate, give claude-push a try and let me know what you think.
https://github.com/coa00/claude-push