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.

CC Dispatch architecture diagram iTerm2 panes connect through the iTerm2 Python API to server.py. Statusline and cc-state hooks write fleet status files that server.py also reads. server.py talks to the PWA on your phone over WebSocket and HTTP via tailscale serve, and pushes notifications through web-push. iTerm2 panes Claude / Codex / Grok iTerm2 Python API screen + transcript read server.py aiohttp statusline.sh cc-state.sh /tmp/cc-status fleet status JSON tailscale serve TLS termination PWA on phone WebSocket + HTTP web-push VAPID

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:

  1. Phone taps Approve in the PWA.
  2. The request carries your passkey session cookie.
  3. PWA sends a POST to the server.
  4. Server runs a gate check — is_claude_pane — to confirm the target pane is actually running a known agent.
  5. Server sends the keystroke via the iTerm2 Python API.
  6. The pane repaints.
  7. 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

1An agent in a pane runs dispatch-auth request <service>.
2The request lands in the vault's pending queue and a push notification goes to your phone.
3You review and tap Approve in the PWA.
4The server decrypts the secret from .integrations.vault using the Fernet key held in the macOS keychain.
5The secret is released just-in-time to the waiting dispatch-auth call — never written to disk in plaintext.

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 varDefaultPurpose
DISPATCH_PORT8788Server port.
DISPATCH_BIND127.0.0.1Bind address. Leave on loopback.
CC_FLEET_DIR/tmp/cc-statusWhere pane status JSON files are read.
VAPID_SUBmailto:admin@example.comContact mailto: for web-push.
WHISPER_BINwhisper-cliwhisper.cpp binary.
WHISPER_MODELmodels/ggml-base.en.binLocal 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.