Control Claude Code from your phone.
Get push notifications when Claude needs you, then tap to open a full terminal on your phone — approve permissions, answer questions, or keep working from anywhere.
Computer: Phone:
┌──────────────────┐ ┌────────────────────┐
│ tmux session │ │ Browser │
│ └── claude │◄── PTY ──► ttyd ──WebSocket──► xterm.js │
│ │ │ (full terminal) │
└──────────────────┘ └────────────────────┘
│
Claude Code hooks ──POST──► Server ──ntfy──► Phone notification
│ "Claude needs you"
└── serves landing page + QR code
- ttyd shares your tmux session as a web terminal on your phone
- Claude Code hooks detect when Claude needs attention
- Server tracks session state, sends notifications via ntfy
- If the terminal page is open on your phone, you get vibration + sound instead of a push notification
- macOS or Linux
- Docker
- tmux (
brew install tmux/apt install tmux) - ttyd (
brew install ttyd/apt install ttyd) - Python 3 (for hooks — stdlib only, no pip packages needed on host)
- ntfy app on your phone (Android/iOS)
git clone https://github.com/JanJetze/claude-remote.git
cd claude-remote
./setup.shThe setup script will:
- Check that tmux, ttyd, and Docker are installed
- Build the Docker image
- Generate auth tokens, TLS certificate, and VAPID keys
- Create config at
~/.config/claude-remote/config.json - Tell you what Claude Code hooks to add (if not already configured)
Follow the printed instructions, then re-run ./setup.sh to verify and install.
cd myproject
claude_remoteThis starts the server + ntfy via Docker, opens Claude Code in a tmux session, and starts ttyd.
- Open the landing page URL printed by
claude_remote(or scan the QR code) - Tap "Subscribe in ntfy app" to get push notifications
- Tap "Open Terminal" to access the full terminal
- Start working with Claude, then walk away
- Phone buzzes via ntfy: "Permission: Bash — npm test"
- Tap the notification — terminal opens in your browser
- Approve the permission, type a response, or just check progress
- Continue from phone or go back to desk
claude-remote uses three Claude Code hooks to detect when Claude needs your attention. The hooks are added to ~/.claude/settings.json and fire for all Claude Code sessions, but only send notifications for sessions started via claude_remote.
| Hook | Matcher | What it catches | Timing |
|---|---|---|---|
Stop |
"" (all) |
Claude finished responding and is waiting for input: questions, choices, plan approval, task completion | Immediate |
PermissionRequest |
"" (all) |
Claude needs permission to run a tool (Bash, Edit, etc.) | Immediate |
Notification |
idle_prompt |
Claude has been idle for 60 seconds waiting for input | After 60s |
Why all three?
Stopgives you an immediate notification whenever Claude stops and waits — this is the primary hookPermissionRequestprovides a more specific notification with the tool name and command (e.g., "Permission: Bash — npm test")Notificationwithidle_promptacts as a backup reminder if you missed the initial notification
setup.sh will print the exact JSON to add. Here's the structure:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/claude-remote/hooks/permission.py",
"async": true
}
]
}
],
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/claude-remote/hooks/notification.py",
"async": true
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/claude-remote/hooks/stop.py",
"async": true
}
]
}
]
}
}The hooks live in ~/.claude/settings.json (global), so they run for every Claude Code session on your machine. However, they only send notifications for claude-remote sessions:
bin/claude_remotelaunches Claude with theCLAUDE_REMOTE=1environment variable- Each hook checks for this variable and exits immediately if it's not set
- Regular
claudesessions are never affected
Generated at ~/.config/claude-remote/config.json:
{
"server_port": 7778,
"ttyd_port": 7779,
"ntfy_port": 2586,
"auth_token": "<generated>",
"host": "0.0.0.0",
"tls_cert": "tls/cert.pem",
"tls_key": "tls/key.pem",
"vapid_public_key": "<generated>",
"vapid_private_key": "<generated>"
}| Component | Description |
|---|---|
bin/claude_remote |
Entry point: starts Docker services, ttyd, and Claude in tmux |
src/claude_remote/ |
Python server: landing page, session tracking, ntfy notifications |
hooks/ |
Claude Code hooks that notify the server (permission, stop, idle) |
docker-compose.yml |
Runs the server + ntfy containers |
- Everything runs on your local network — no external services except ntfy push delivery
- Auth token required for all API endpoints
- Self-signed TLS certificate generated automatically
- For access outside your LAN, use Tailscale or similar VPN
- Pause notifications: Toggle on the landing page — server stays running, no push alerts
- Stop everything:
docker compose downin the repo directory - Remove hooks: Delete hook entries from
~/.claude/settings.json - Uninstall: Remove
~/.local/bin/claude_remote,~/.config/claude-remote/, and the hook entries
MIT