How CC Dispatch works
Overview
CC Dispatch is a small aiohttp server that sits next to iTerm2 on your Mac and reads the panes that are already running Claude Code, Codex or Grok. It polls each pane's screen and transcript through the iTerm2 Python API, exposes a fleet view over HTTP and WebSocket, and — only in response to an authenticated request from the installed PWA — sends keystrokes back into a pane. It never launches, restarts, or kills a session; if you stop the server, your terminals are untouched. Reachability comes from Tailscale, which terminates TLS and proxies a single loopback port to your Tailnet, and the only way in is a passkey registered from a one-time bootstrap QR code.
Architecture
Two data paths feed the server: the live iTerm2 panes themselves, and a side channel of fleet-status files written by Claude Code hooks. Everything the server produces flows out to the PWA over Tailscale, including push notifications that don't require the app to be open.
Components
server.py
The aiohttp server. Polls iTerm2 for pane contents, reads each
session's transcript (Claude's .jsonl, Codex's rollout,
Grok's chat log), exposes the fleet over HTTP/WebSocket, and sends
keystrokes back to panes confirmed to be running one of those agents.
Permission mode, effort and model controls are Claude-only and hidden
for the others.
auth.py
WebAuthn/passkey login. The bootstrap token baked into the startup QR is single-use, for registering the first passkey; after that, only passkeys get in.
vault.py + dispatch-auth
The credential broker. An agent in a pane runs
dispatch-auth request <service>; you approve it on
your phone; the secret is released just-in-time and never touches
disk in plaintext.
static/ (PWA + sw.js)
The installable PWA: the fleet view, chat view, and a service worker that receives web-push notifications even when the app isn't open.
examples/ hooks
Ready-to-use statusline.sh and cc-state.sh
scripts that make every Claude Code pane show up in the fleet view
without waiting on a foreground-job check.
Request lifecycle
What happens when you tap Approve on a permission prompt:
- Phone taps Approve in the PWA.
- The request carries your passkey session cookie.
- PWA sends a
POSTto the server. - Server runs a gate check —
is_claude_pane— to confirm the target pane is actually running a known agent. - Server sends the keystroke via the iTerm2 Python API.
- The pane repaints.
- The server's poll loop (or an open WebSocket) pushes the new screen content back to the phone.
Fleet status pipeline
Claude Code's statusLine hook runs
statusline.sh, which writes a JSON status file — tagged
with the pane's iTerm2 session id — into CC_FLEET_DIR
(default /tmp/cc-status). UserPromptSubmit,
Stop and Notification hooks call
cc-state.sh to flip that pane between
working and idle. The server watches that
directory and merges it with what iTerm2 reports directly, so a new
pane shows up in the fleet within a couple of seconds of its first
repaint. Panes without a status file are still driveable whenever
iTerm2 reports Claude as the foreground job — the hooks just make the
fleet view complete.
Auth flow
On first run the server prints a QR code containing a URL with a
single-use bootstrap token. Scanning it and completing WebAuthn
registration creates your passkey; the bootstrap token is then burned
and cannot register another one. Every visit after that authenticates
with the passkey alone. WebAuthn requires a real HTTPS origin, which
is why tailscale serve — not a bare IP — sits in front of
the server: passkeys can't be registered against a plain HTTP or raw-IP
origin, so this also rules out exposing the port directly.
Credential broker flow
Notifications
The PWA registers for web-push using VAPID keys. When a session finishes or stalls waiting on input, the server pushes a notification through the browser's push service, so you find out without the app open or your phone on the same network.
Voice
The /api/whisper endpoint transcribes recorded audio
locally using whisper.cpp — nothing leaves the machine. It's used for
dictating follow-ups from the phone's chat view instead of typing on
glass.
Operations
A launchd watchdog keeps the server alive across reboots and restarts
it if it gets stuck. An auto-deploy loop mirrors origin/main
and sends the running process a SIGHUP to hot-reload
without dropping the passkey session. The server must be launched from
inside an iTerm2 session — it inherits ITERM2_COOKIE from
the environment, which is what the iTerm2 Python API uses to
authorize a script talking to it.
Configuration
| Env var | Default | Purpose |
|---|---|---|
DISPATCH_PORT | 8788 | Server port. |
DISPATCH_BIND | 127.0.0.1 | Bind address. Leave on loopback. |
CC_FLEET_DIR | /tmp/cc-status | Where pane status JSON files are read. |
VAPID_SUB | mailto:admin@example.com | Contact mailto: for web-push. |
WHISPER_BIN | whisper-cli | whisper.cpp binary. |
WHISPER_MODEL | models/ggml-base.en.bin | Local transcription model. |
Threat model — what it does not protect against
CC Dispatch trusts the local Mac and your Tailnet. It is not a defense against a compromised device or network.
- Anyone holding your unlocked phone with the passkey enrolled can drive the fleet.
- A compromised Tailnet (a malicious or stolen node with access to your tailnet) can reach the proxied port.
- It trusts the local Mac completely — anything with access to the machine's iTerm2, filesystem, or keychain can reach what CC Dispatch can reach.
- It gates writes to panes it recognizes as a known agent, but does not sandbox or review what that agent is instructed to do once a keystroke lands.
FAQ
Does it work on Linux?
No. It's built around the iTerm2 Python API, which is macOS-only.
Does it restart my sessions?
No. It only ever attaches to panes that are already running. Stopping the server leaves your terminals untouched.
Where do secrets live?
Integration secrets sit encrypted in .integrations.vault,
decrypted only in memory with a Fernet key stored in the macOS login
keychain. All per-install secrets and state are git-ignored.