Documentation
Everything from first install to running your own relay. Most people are done after the first three sections — the rest is here when you want it.
Requirements
- A Mac with Claude Code installed and signed in.
- An iPhone for the companion app (two-way features are iOS-only).
- Nothing else. No account signup — pairing creates an anonymous, random account identifier.
Install the Mac app
With Homebrew:
brew install --cask claude-code-notifier
Or grab the direct download from the homepage, unzip, and drag it to Applications. Launch it and you’ll see the bell icon in your menu bar.
The app runs a small HTTP server on 127.0.0.1:43210 (the port is configurable in settings). This server only listens on localhost — it is what Claude Code’s hooks talk to.
Install the hooks
Click Install Claude Code Hooks in the menu bar app. That’s the whole step. Behind the scenes, it merges the following into ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 5 }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 5 }] }
],
"PermissionRequest": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 590 }] }
],
"Notification": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 10 }] }
],
"Stop": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 10 }] }
],
"SessionEnd": [
{ "hooks": [{ "type": "http", "url": "http://127.0.0.1:43210/hook", "timeout": 5 }] }
]
}
}
Each entry is marker-tagged by the app, so your existing hooks are never touched and uninstalling removes exactly what was added — nothing else.
What each hook is for:
| Event | Timeout | Purpose |
|---|---|---|
SessionStart | 5 s | Register the session (working directory, source) |
UserPromptSubmit | 5 s | Timeline: what you asked |
PermissionRequest | 590 s | Two-way approve/deny — held open while waiting for your decision |
Notification | 10 s | Idle, permission, and agent notifications |
Stop | 10 s | “Claude finished” plus the last message |
SessionEnd | 5 s | Mark the session closed |
Fail-open, always. If the Mac app isn’t running, these HTTP hooks fail as non-blocking errors and Claude Code continues normally. The notifier can never block or break a session.
Pair your iPhone
- Install the iOS app on your iPhone.
- On the Mac, choose Pair a Device… from the menu bar. A QR code appears.
- Scan it with the iOS app. Done.
What’s in the QR code matters. It carries the account ID, a single-use pairing code (8 characters, valid for 10 minutes), the relay URL — and the 32-byte end-to-end encryption key, generated on your Mac moments earlier:
ccnotifier://pair?v=1&a=<accountId>&c=<pairCode>&k=<base64url 32-byte key>&u=<https api base>[&n=<mac name>]
The key (k) travels only across the air gap between your Mac’s screen and your iPhone’s camera. It is never sent to the relay, which is why the relay can never read your data. Pair additional iPhones or iPads the same way; each Mac maintains its own pairing and key.
Multiple Macs: scan each Mac’s QR code from the same phone (iOS app → Settings → Pair another Mac). Sessions are grouped per Mac — named from the QR code — each with its own encryption key, and you can rename or unpair any Mac individually. Unpairing removes the phone from that Mac’s relay account, so its notifications stop immediately.
iOS setup
- Allow notifications when prompted — this is the entire point of the app.
- Permission and attention notifications are delivered as Time Sensitive, so they can break through Focus modes if you allow it (Settings → Focus → your Focus → add Claude Code Notifier).
- Notification content is decrypted on your phone by a Notification Service Extension. If decryption ever fails, you’ll see a generic “Claude Code” notification instead of the real content — see troubleshooting.
- Each Claude Code session appears as a timeline in the app: prompts, permission requests, and results in order.
Notifications & actions
Long-press (or pull down) a notification to act on it without unlocking:
| Notification | Actions |
|---|---|
| Permission request | Approve (requires Face ID / Touch ID / passcode) · Deny · Deny with reason — type why, and Claude reads it |
| Claude finished | Reply — type a follow-up; the session resumes on your Mac |
| Needs attention | Reply |
| Info | None — purely informational |
First answer wins. A permission request can be answered from the Mac notification, the menu bar, or the phone — whichever responds first decides, and the others are dismissed. If nobody answers within the remote decision window (540 seconds by default), the request defers back to the normal terminal dialog. Nothing is ever auto-approved.
Remote replies
When you reply from the phone, the Mac app finds the session’s working directory and resumes it headlessly:
claude -p --resume <sessionId> --output-format json -- "<your message>"
Because the headless run fires the same hooks, everything loops: if Claude needs a new permission, that request is pushed to your phone; when Claude finishes, the answer is pushed to your phone. You can carry a full conversation without going near the desk.
Note on the free tier: receiving notifications is always unlimited. Sending remote responses (approve, deny, reply) is capped at 3 per day on the free iOS tier — Pro removes the cap.
Presence & quiet hours
The Mac app considers you away when the screen is locked or there has been no keyboard/mouse input for a threshold you choose (default: 120 seconds).
- At the Mac: events become local macOS banners. No push, no double-ping.
- Away: events are encrypted and pushed to your iPhone.
- Always push is available if you want the phone notified regardless.
- Quiet hours suppress pushes on both ends during hours you set.
Self-hosting: deploy the Worker
The entire backend is one Cloudflare Worker plus one Durable Object per account, storing only ciphertext. Running your own costs little to nothing on the Workers free tier and removes touchtap from the loop entirely.
# placeholder repository URL — see the homepage footer for the real one
git clone https://github.com/touchtap/claude-code-notifier
cd claude-code-notifier/backend
npm install
npx wrangler deploy
Verify it’s up:
curl https://<your-worker>.workers.dev/healthz
# → { "ok": true, "version": "…" }
Self-hosting: APNs credentials
The relay delivers pushes through Apple’s Push Notification service using token-based auth (a .p8 signing key, ES256, over HTTP/2). Set your credentials as Worker secrets:
npx wrangler secret put APNS_TEAM_ID # your Apple Developer Team ID
npx wrangler secret put APNS_KEY_ID # the Key ID of your .p8
npx wrangler secret put APNS_PRIVATE_KEY # paste the .p8 file contents
npx wrangler secret put APNS_BUNDLE_ID # the iOS app's bundle identifier
Heads-up: APNs keys are tied to an Apple Developer team, so pushes to an iOS app must be signed by the team that built it. Self-hosting the relay works with your own build of the iOS app and your own .p8. Check the backend README in the repository for the authoritative secret names for your version.
Self-hosting: point the apps at it
- In the Mac app’s settings, set the relay URL to your Worker’s address.
- Re-pair your iPhone. The pairing QR embeds the API base URL (the
u=parameter), so the iOS app follows automatically — no configuration on the phone.
Everything else — encryption, pairing, ring buffer, reply queue — behaves identically. It’s the same code.
Troubleshooting
Is the Mac app healthy?
curl http://127.0.0.1:43210/health
# → { "ok": true, "version": "…", "sessions": 2 }
If this fails, the app isn’t running or is on a different port (check the app’s settings). Remember: while it’s down, Claude Code works normally — you just get no notifications.
No pushes arriving on the phone
- You’re at your Mac. Presence detection routes events to local banners while you’re active. Lock the screen or enable Always push to test.
- Quiet hours may be active on either device.
- Check pairing status in the iOS app — it shows whether your Mac is currently online.
- Confirm notifications are allowed in iOS Settings, and that your current Focus mode admits Time Sensitive notifications from the app.
Notifications show “Claude Code — Notification” with no detail
That generic text is the deliberate fallback when the phone can’t decrypt the payload — usually a stale or mismatched key (for example, after re-pairing the Mac). Fix: unpair and scan the current QR code again to receive the current key.
Permission requests never reach the phone
- Verify the hooks are installed: menu bar → Hook Status, or look for the
PermissionRequestentry in~/.claude/settings.json. - Hooks apply to new sessions — restart any Claude Code session that was already running when you installed.
Port 43210 is taken
Change the port in the Mac app’s settings. The app rewrites its hook entries in ~/.claude/settings.json to match.
Uninstall
- In the menu bar app, choose Remove Claude Code Hooks. Because every entry it wrote is marker-tagged, this restores
~/.claude/settings.jsonexactly — any hooks of your own are untouched. - Unpair devices (menu bar → Devices → remove) to revoke their tokens on the relay.
- Remove the app:
…or drag it from Applications to the Trash.
brew uninstall --cask claude-code-notifier - Delete the iOS app from your iPhone.
The relay’s ring buffer only ever holds your last 1,000 encrypted envelopes; with the key deleted alongside the apps, that residue is unreadable by anyone — including us. See the privacy policy for retention details.