Approve Codex permission prompts from your Apple Watch while Codex runs in tmux.
When Codex asks for command approval, the watcher sends a push notification and a one-tap Watch Shortcut approves the prompt through a secure webhook.
Codex runs inside tmux on a Linux server.
A watcher script monitors the tmux output.
When an approval prompt appears:
- The server sends a push notification (Pushover).
- Tapping Approve hits a secure webhook.
- The webhook injects the approval keystroke back into
tmux. - Codex continues execution.
- Codex (
tmuxsession): Codex runs inside a persistenttmuxsession so it keeps running if SSH disconnects. - Watcher (
codex_watch.sh): polling loop usingtmux capture-paneto detect approval prompts and send Pushover alerts. - Webhook (
approve_webhook.py): local HTTP server receiving/approveactions and injecting keys into Codex viatmux send-keys. - Tailscale Serve (HTTPS bridge): exposes the local webhook over your tailnet using your
*.ts.netHTTPS URL without opening public ports. - Apple Watch Shortcut ("codex approve"): watch action that performs a simple
GETrequest to approve. - Pushover: notification channel for reliable phone/watch alerts.
- Pushover = alerts (phone + watch notifications)
- Shortcuts = action (watch executes the approval request)
- Codex shows an approval prompt.
codex_watch.shdetects it and sends a Pushover notification.- You glance at your watch.
- You run the
codex approveShortcut on the watch. - The Shortcut calls the Tailscale HTTPS endpoint.
approve_webhook.pyvalidates the secret and injects approval into the Codextmuxsession.- Codex continues running the command.
The logic runs on the server, not on your laptop or phone.
- Code from your laptop via SSH
- SSH from your phone
- Close your laptop completely
As long as Codex + watcher are running on the server, approvals will reach your iPhone and Apple Watch. Devices are just approval interfaces — the server does all the work.
- Webhook stays local and is exposed to your devices via Tailscale HTTPS (tailnet-only).
- Approval requires a shared secret (
APPROVE_SECRET). - Secret can be sent as
?secret=..., which works well for browser and Watch Shortcut flows. X-Secretheader is also accepted for CLI and automation compatibility.- No public port exposure is required.
tmux keeps Codex persistent. You can close Termius/SSH and Codex keeps running. The webhook injects keys into the target tmux pane, so approvals still work when you are not actively connected.
python3tmuxcurl- Linux shell environment with access to
/tmp
cd /opt/codex-control
cp .env.example .env
Edit .env and set at minimum:
APPROVE_SECRETPUSHOVER_APP_TOKENPUSHOVER_USER_KEY
Never commit .env.
APPROVE_SECRET(required): shared secret used by webhook auth.PUSHOVER_APP_TOKEN(required): Pushover application token.PUSHOVER_USER_KEY(required): destination Pushover user key.TMUX_SESSION(defaultcodex:0.0): target pane/session fortmux send-keysandcapture-pane.APPROVE_HOST(legacy): configured host value; currentapprove_webhook.pybuild binds to a fixed host instead.APPROVE_PORT(default8787): webhook bind port.CODEX_WATCH_LOG(default/tmp/codex_watch.log): watcher log path.LAST_SENT_FILE(default/tmp/codex_approval_last_sent): cooldown state file.COOLDOWN_SECONDS(default30): minimum time between push notifications.APPROVE_URL(optional): override destination link; secret query param is auto-added if missing.
The current approve_webhook.py version binds to a fixed host configured in code and uses ${APPROVE_PORT} for the port.
For public repos, avoid documenting private or internal host addresses; keep host-specific values in local-only changes.
Terminal 1:
cd /opt/codex-control
set -a; source .env; set +a
python3 approve_webhook.py
Terminal 2:
cd /opt/codex-control
bash ./codex_watch.sh
cd /opt/codex-control
bash ./restart.sh
All endpoints require the secret via either:
X-Secret: <APPROVE_SECRET>header, or?secret=<APPROVE_SECRET>query parameter.
Supported paths:
/approve: sendsy+ Enter./approve2: sendsp(and Enter onGET)./deny: sendsesconGET, sends3onPOST.
approve_webhook.py currently listens on the fixed host configured in code and ${APPROVE_PORT}.
pkill -f approve_webhook.py
pkill -f codex_watch.sh
To stop notification polling only:
cd /opt/codex-control
bash ./stop.sh
- Watcher log:
${CODEX_WATCH_LOG}(default/tmp/codex_watch.log) - Webhook log (if started with
restart.sh):/tmp/approve_webhook.log
Quick checks:
ss -ltnp | grep 8787
ps aux | grep -E "approve_webhook.py|codex_watch.sh" | grep -v grep
tmux attach -t codex
If your session target differs, use the value of TMUX_SESSION.
- Landing page — codex-control.clawnux.com (hosted on Vercel)
- Webhook + watcher — runs on your own Linux server, exposed privately via Tailscale
The landing page is a standalone site that documents the project. The webhook and watcher run locally and are never exposed to the public internet.
- Landing page live at codex-control.clawnux.com.
approve_webhook.py: validates?secret=query parameter directly and still acceptsX-Secretheader for compatibility.restart.sh: explicitly sources/opt/codex-control/.envbefore starting services, avoiding dependence on caller working directory.