Run setup
npx tapproval setup registers a device, installs the hook and
prints a QR to scan. Once per machine — then restart your agent.
Your agent's permission prompt goes to your phone. You tap allow or deny, and it keeps working.
tapprovalnpm test -- webhook
From install to a prompt on your lock screen in about a minute.
npx tapproval setup registers a device, installs the hook and
prints a QR to scan. Once per machine — then restart your agent.
Instead of blocking on a terminal nobody is watching, the permission prompt is encrypted and pushed to your phone.
Allow or deny, straight from the lock screen. The decision goes back to the hook still holding your agent open, and it carries on.
The hook is just a subprocess: it reads the tool call on stdin and writes your decision back on stdout. One hop out, one hop back — and the payload is sealed before either of them.
Your command is encrypted on your machine before it is sent, so what the
server stores is an opaque blob. Prefer not to trust that? Run the whole
thing yourself with setup --self-hosted.
deviceId — a random uuidBash, Write, Edit| tapproval setup | register, install the hook, print a QR |
| tapproval pair --ios | iPhone: install the app first, then type a code |
| tapproval doctor | diagnose config, hooks, reachability, revoked devices |
| tapproval mute | stop notifications; unmute resumes them |
| tapproval notify Bash Edit | buzz for a shortlist of tools only |
| tapproval notify --grace=60 | hold the push a minute, in case you answer at the keyboard |
| tapproval uninstall | remove the hook |
iOS only allows web push from an app installed to the Home Screen, and a pair code is single-use. Scan the QR in Safari and you pair Safari — not the installed app — and burn the code doing it.
So the order matters. tapproval pair --ios walks you through it:
This is the common one, and the fix is the same every time: pair the phone again, once.
tapproval doctor # confirm the Mac side is healthy
tapproval pair --code # then type the code in the installed app
iOS evicts a PWA's push subscription on its own — after long periods unused, when
storage is reclaimed, on some iOS updates, or if the Home Screen icon was removed
and re-added. Nothing on your machine changed and doctor stays green,
because the subscription died silently on Apple's side. Re-pairing mints a fresh
one.
If it still doesn't buzz: check iOS Settings → Notifications → tapproval is allowed, and that Focus / Do Not Disturb isn't filtering it. As a last resort, delete the Home Screen icon, re-install from Safari, and pair again.
WebKit ignores notification action buttons for web push — unchanged since iOS 16.4. On Android and desktop Chrome you answer with one tap from the lock screen; on iPhone you tap the notification, the app opens, and you answer there. It's one extra tap, and the app recovers the pending request even though iOS drops the notification's target URL.
That phone can't decrypt the payload — it either never got the key or lost it (cleared site data, or it was paired before payload encryption existed). Re-pair and it comes back. The request is still answerable meanwhile; only the preview is missing.
If it instead says "Cannot read this request", the phone holds the
wrong key — usually because setup was re-run and generated a
new one. Same fix: tapproval pair.
The hook isn't registered, or the session hasn't reloaded it. A session started
before install rewrites its settings file from a startup snapshot and deletes the
hook — so restart your agent after setup.
tapproval doctor names the exact failure: config, hook registration, timeout ordering, API reachability, Realtime, revoked devices.
Usually a hook path that no longer exists — the global package was removed without
uninstall, or an older version pointed the hook into an npx cache npm
has since pruned. npx tapproval setup fixes both: it copies the runtime
to ~/.tapproval/runtime/ and repoints the hook there.
The hook fails safe while broken — every prompt falls back to the terminal and nothing is ever auto-approved, which is also why it's easy to miss.
doctor is all green
Check the grace period. With graceSec above 0, a prompt you answer in
the terminal within that window is designed never to reach your phone —
no row, no push, nothing to dismiss. tapproval notify --grace=0 to buzz
immediately.
No. summary, detail and cwd are encrypted on
your machine with AES-256-GCM before anything is sent. The key is generated by the
CLI, handed to each phone once inside the single-use pair code, and deleted from
the server the moment that code is claimed. What lands in Postgres is one opaque
blob; the push itself carries only a generic "Approve Bash?".
Yes. tapproval pair adds another device and asks which platform;
--ios, --android and --code skip the question.
Every paired phone gets the same request, and the first answer wins.
The hook waits timeoutSec (300 by default) and then gets out of the
way — the prompt goes back to the terminal exactly as it would without tapproval.
Silence is never an approval.
Today it hooks Claude Code's permission prompts. Codex support is an open issue and a small adapter away — the phone side is agent-agnostic.
No account, no tunnel, no server to babysit. Works today with Claude Code — Codex support is an open issue and a small adapter away.