Hooks
Install moshi-hook so Claude Code, Codex, OpenCode, Gemini, Cursor, Kimi, and Qwen can talk to Moshi — and unlock context detection, diff view, and browser preview.
moshi-hook is the companion daemon that lets local coding agents report useful events to Moshi. It runs on your host, installs agent hook config, keeps a local Unix socket for hook calls, and maintains a WebSocket connection back to Moshi for approvals and status updates.
The same daemon powers the in-app Diff viewer and Browser preview, and feeds the iOS Live Activity.
New to agent workflows? Best iOS Terminal App for AI Coding Agents covers why hooks, mosh, and push matter, and Moshi Agent Hooks is the deep dive on the inbox, Live Activities, and Apple Watch.
Supported agents
The installer can wire these agents:
- Claude Code.
- Codex CLI.
- OpenCode.
- Gemini CLI.
- Cursor.
- Kimi.
- Qwen Code.
The exact event surface differs by agent, but Moshi normalizes events into a small set of inbox categories.
Install on macOS
Open Settings -> Hooks in Moshi and copy your pairing token. Then run:
$brew tap rjyo/moshi$brew trust rjyo/moshi$brew install moshi-hook$moshi-hook pair --token <token from Moshi>$moshi-hook install$brew services start moshi-hook
On macOS, secrets are stored in Keychain by default. If you pair from SSH and Keychain is locked, unlock it first or use file-backed storage:
$security unlock-keychain ~/Library/Keychains/login.keychain-db$moshi-hook pair --token <token from Moshi>$moshi-hook pair --token <token from Moshi> --store file
Install on Linux
$curl -fsSL https://getmoshi.app/install.sh | sh$moshi-hook pair --token <token from Moshi>$moshi-hook install$moshi-hook serve
For a permanent Linux setup, run moshi-hook serve under your process manager of choice.
Update
On macOS, refresh the tap and upgrade:
$brew update$brew upgrade moshi-hook$brew services restart moshi-hook
On Linux, re-run the install script and restart your moshi-hook serve process:
$curl -fsSL https://getmoshi.app/install.sh | sh
Pairing and installed agent hooks survive an upgrade — no need to re-pair or re-run moshi-hook install.
What install changes
moshi-hook install writes Moshi-owned entries into supported agent config files. It is designed to leave user-owned hooks alone.
Managed locations include:
- Claude Code:
~/.claude/settings.json. - Codex:
~/.codex/hooks.jsonand the needed Codex config flag. - OpenCode:
.opencode/plugins/moshi-hooks.tsin the current project. - Gemini:
~/.gemini/settings.json. - Cursor:
~/.cursor/hooks.json. - Kimi:
~/.kimi/config.toml. - Qwen:
~/.qwen/settings.json.
Use moshi-hook uninstall to remove Moshi-owned entries.
What needs moshi-hook serve
moshi-hook is both a CLI and a long-running daemon. Some features rely on the daemon being up (it owns the local Unix socket that agents post events to, and the gateway on 127.0.0.1:24543 that the iOS app forwards to); others are one-shot CLI calls that work whether or not the daemon is running.
Needs moshi-hook serve running:
- Agent event delivery: Claude Code, Codex, OpenCode, Gemini, Cursor, Kimi, and Qwen all post hook events to the local Unix socket. Without the daemon, events go nowhere.
- Inbox notifications, Live Activity, and approval round-trips.
- In-app Diff viewer opened from a Moshi terminal session (the iOS app talks to the gateway port
24543). - In-app Browser preview.
- Live multiplexer detection — the signal that drives the swipe-to-switch-window gesture and the "in tmux/Zellij/Herdr right now" badge on a session. SSH preflight alone only tells Moshi the multiplexer is installed; the daemon's gateway tells it whether the current session is inside one.
Works without moshi-hook serve:
moshi <dir>— the project tmux launcher. Pure exec, no daemon.moshi diff <dir>— the standalone diff viewer opened in your laptop browser. Spawns its own short-lived server and exits when you close it; coexists with the daemon by falling back to an ephemeral port if24543is already taken.moshi-hook context— one-shot terminal-context probe. Reads$TMUX_PANE,$ZELLIJ,$HERDR_ENVfrom your own shell and prints the detected kind, session, pane, and cwd as JSON. Useful for debugging the tmux swipe gesture.moshi-hook pair,install,uninstall,status,update,version— all daemon-less.
The short rule: anything that streams state to the iOS app or accepts pushes from a coding agent needs serve; anything you invoke once from a shell does not.
Inbox categories
Moshi forwards a deliberately small set of events:
Moshi's inbox keeps one active row per agent session. New events for the same session update the row instead of creating a long stack of notifications.
Data privacy
moshi-hook is built so that the heavy, sensitive data stays between your host and your phone, and only small notification summaries touch Moshi's server.
Stays local, or goes directly between host and app:
- Full Claude Code and Codex transcripts. When you open a session's conversation in Moshi, the transcript streams from the host to the phone through the SSH-forwarded local gateway — it never passes through the Moshi backend.
- Diff payloads. The Diff viewer reads changes locally on the host and serves them to the app over the same local gateway.
- Your source files. File contents are never part of the cloud agent-event payload.
- Terminal traffic. Your actual sessions run over SSH, Mosh, or your Tailscale tailnet, directly to the host.
Reaches Moshi's server:
- Notification and inbox event summaries — the inbox categories above.
- Up to 200 characters of your prompt, used as the event body.
- Up to 80 characters of the assistant's response, used as the event title.
- The command or question behind an approval request, up to 256 characters, so you can decide from the notification.
- Metadata: project name, session ID, agent, model, tool name, terminal identifiers, account ID, and context-window percentage.
- Pairing, usage synchronization, approval decisions, and WebSocket control traffic.
Everything sent to the server is what you see rendered in a push notification or inbox row — nothing more is collected. For how the app itself stores credentials and what iCloud sync covers, see Security and sync.
Third-party harnesses
Custom harnesses that are not installed through moshi-hook, such as oh-my-pi notification plugins, can use Moshi's legacy compatibility endpoint:
curl -X POST https://api.getmoshi.app/api/v1/agent-events \
-H "Authorization: Bearer $MOSHI_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source": "opencode",
"eventType": "notification",
"sessionId": "omp-session-1",
"category": "task_complete",
"title": "Task complete",
"message": "oh-my-pi finished a turn",
"eventId": "omp-event-1"
}'
Use category values from the table above. approval_required events should include pendingActionId, expiresAt, and hostId only when the harness can also route decisions back to the running host. This endpoint is direct-only and does not support unified: true.
This legacy endpoint is scheduled for retirement on June 15, 2026. After that date, it returns 410 legacy_agent_events_retired.
Host-resident daemons should prefer POST /api/v1/hosts/:hostId/events with the host secret; custom unified pushes should use POST /api/webhook with unified: true.
Host status dot
When Show hook status is on, each saved host carries a small colored dot on its server icon, reflecting the last moshi-hook probe. The same five states drive the filter chips above the host list:
Tapping a host's dot opens a sheet that explains the state and, when something needs attention, gives copy-paste fix commands for macOS or Linux. You can turn the dots off in Moshi's settings.
Moshi runs the probe over a non-interactive login shell and resolves moshi-hook (and the moshi alias) from ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, and /opt/local/bin, then from your login shell's own PATH. A standard Homebrew or install.sh install lands in one of those directories, so it is detected without any extra PATH setup. The running vs not running call is authoritative: Moshi opens a real connection to the gateway port rather than trusting a process name.
A host that shows not installed when moshi-hook really is installed usually means a custom install location — see moshi-hook is installed but shows as not installed.
Diagnostics
Useful commands:
$moshi-hook status$moshi-hook status --json$moshi-hook logs -f$moshi-hook usage --sync
If hooks are installed but Moshi does not update, check that moshi-hook serve or the macOS service is running and paired to the same Moshi account/token shown in the app.