Fleet Deck ⚡

One reliable control plane for every Claude Code session on your machine.
Run more than two or three Claude Code sessions and you lose track of them: which terminal is which, which one is blocked on a permission prompt, and which two are editing the same file. Fleet Deck puts all of them on one local board at http://127.0.0.1:4711, with a callsign, a live status column, and a mailbox each.
Fleet Deck is deliberately an accessory, never a dependency of the developer loop. If its daemon,
board, network, or hooks are unhealthy, Claude Code must remain usable from the terminal. That rule is
the reliability boundary every feature below is built around.
What it does
- Live status, derived not self-reported.
queued → working → verifying → needs-you → idle → offline, computed from hook telemetry. The model badge is read from the session transcript, so it follows a mid-session /model switch instead of freezing on whatever the session launched with.
- Conflict radar. Two sessions touching the same file within 30 minutes get warned in context, and the board flashes hazard-red. Worktree-aware: the same file edited in two worktrees of one repo is a merge conflict surfacing early.
- Needs-you rail. Permission prompts, multiple-choice questions, MCP forms and trailing questions from Fleet Deck-owned Claude panes become cards you answer from the board. The terminal prints
⎿ Allowed by PermissionRequest hook and continues. Outlast the default 10-minute answer window and the question re-arms: a fresh card (badged RE-ARMED) whose answer is sent as a message, delivered at the next turn boundary — it cannot unblock an agent parked on its own prompt, and never pretends to. Ordinary terminal sessions use Claude's native prompts by default.
- Mail between sessions. Message one session, a repo, or everyone. Delivered at the next turn boundary; idle sessions are woken by a small watcher, usually within seconds.
- Routing without a model call.
assign auto: fix the flaky test picks a candidate — idle first, least buried, right repo — with a SQL query. The core makes zero model calls.
- Spawning.
+ Spawn starts a fresh interactive claude in a daemon-owned tmux window. Watch it on the board, attach to the pane, or kill it.
- A live terminal in the browser. Click a spawned session's tmux chip and its pane opens as an xterm.js terminal bridged over WebSocket to
tmux -C attach. No PTY, no native deps. Typing sends real keystrokes to the agent's pane. Shift+Enter inserts a newline rather than submitting — the board is the emulator, so it sends the ESC CR that Claude Code's keybinding expects, with nothing to configure. Ctrl+V pastes a screenshot: the board uploads the image, writes it server-side, and types the path into the composer; you press Enter yourself. (PNG/JPEG/GIF/WebP, ≤10 MB, written owner-only under FLEETDECK_HOME, pruned after 24 h or 50 files.)
- A terminal grid.
▦ Terminals opens every live agent at once. All tiles stream; exactly one accepts input — the focused tile wears an amber ring, and every other tile is stdin-disabled at the terminal itself, not filtered on the way out. The grid shares one tmux control client, so the tenth tile costs a WebSocket, not a tenth tmux process.
- Revive. Worktrees and transcripts outlive panes. One
⟲ click resumes a dead agent in its own worktree with full history — same callsign, same card. Whole columns at a time.
- Remote control. Hand a session to claude.ai and drive it from a phone. The card grows a 📱 chip named after the callsign.
- A plan library. Spawn a planner in plan mode; its plan lands on the board as a rendered card before it can act. Approve it, or capture it and release the planner. Execute it later with your own instructions.
- Workspace tools. Browse and search files, inspect worktrees, rename sessions, filter the event feed, and share an already-proxied board without opening the daemon to the network.
- A board that survives real developer use. Requests reject visibly, destructive actions are single-flight, state reconnects after daemon restarts, and the layout collapses to a usable phone-sized view instead of hiding controls off-screen.
Install
claude plugin marketplace add lacion/fleet-deck # the repo is its own marketplace
claude plugin install fleetdeck@fleetdeck
Your next claude — any terminal, any repo, no wrapper — brings the fleet up and appears on the board. Type /fleet in any session for a live summary.
How updates actually reach you. Claude Code installs a marketplace plugin by copying it into a versioned local cache, and that cached copy is what runs at every SessionStart — not the repo. Auto-update is off by default for third-party marketplaces, and even with it on, Claude Code only recognizes an update when the plugin's version changes in the manifest. Fleet Deck bumps version in .claude-plugin/plugin.json and .claude-plugin/marketplace.json for every distributable change for exactly this reason. To pull a new version, run claude plugin marketplace update fleetdeck and claude plugin update fleetdeck@fleetdeck (or manage it from /plugin), then start a new session. A push to main without a version bump never reaches an installed copy. If you want releases only, use the npm channel — npm i -g fleetdeck for standalone mode — or pin your marketplace clone to a tag.
Requirements. Bun 1.3.14+ — the CLI, hook shims, and daemon all run under Bun. A plugin install has no dependency-install step: the daemon ships as one bundled file and keeps state in Bun's built-in bun:sqlite. Add tmux 3.4+ to spawn workers or open their panes in the browser — Fleet Deck relies on 3.4's no-start probe to avoid attaching to a replacement server. Everything else works without tmux. Linux, WSL2 and macOS; Windows-native is untested.
Fleet Deck does not own your Claude Code version. Keep using Claude's normal latest, stable, or exact-version install flow, including upgrades and downgrades. This Fleet Deck release enables its hooks for stable Claude Code 2.1.206 and newer. An older, prerelease, or unidentifiable CLI makes every Fleet Deck hook a silent no-op: Claude starts and behaves normally, with no Fleet Deck warning or injected model context. Newer stable releases stay enabled; Fleet Deck's strict response validation and fail-open launcher prevent an incompatible hook result from interrupting or adding noise to Claude. For a decision hook, the neutral no-op is exactly {} on stdout, empty stderr, and exit code 0; Claude receives no override and continues to its native prompt or normal tool behavior. SessionStart and the background watcher use the equivalent empty-stream exit 0. The machine-readable minimum is compatibility.json.
Working on Fleet Deck itself, or running from a fork
claude plugin marketplace add /path/to/fleet-deck # a local clone
claude plugin marketplace add your-org/your-fork # or your fork
claude plugin install fleetdeck@fleetdeck
After changing daemon, CLI, hook, or board source, rebuild the matching distributable — the installed
plugin and npm package run generated artifacts, not the TypeScript source. The complete command set is
in Development. Then restart the daemon, or bump the version and let the verified
upgrade handoff do it.
Two traps, both caused by the versioned plugin cache described above:
- A rebuilt clone is not a reinstall. If you installed from this clone as a marketplace, Claude Code runs the cached copy from install time, not your working tree. After rebuilding, run
claude plugin marketplace update fleetdeck and claude plugin update fleetdeck@fleetdeck — and bump the version in both manifests first, or the update is a no-op and you will validate the stale cache.
- To run the working tree directly, skip the cache entirely:
claude --plugin-dir /path/to/fleet-deck. That session's hooks come from your checkout, so what you test is what you just built.
Quick start
- Open the board and launch a
claude anywhere. A card appears in about a second.
- Launch a second session in the same repo and have both touch the same file. Watch the hazard ripple and read the whisper each session receives.
- Compose → ORCHESTRATOR →
assign auto: add input validation to the signup form. The daemon picks a session and wakes it if idle.
- If nobody is available, the board offers spawn a session for this — one click, prefilled with the task.
- Tick batch on the spawn form and paste three tasks, one per line: three agents, three worktrees, three branches. Then hit ▦ Terminals.
- Spawn a planner (
permission mode: plan), capture its plan, and execute it later with an unsupervised worker.
Batch spawn
Tick batch and the prompt box becomes a task list — one agent per line:
fix the flaky worktree test
update the README install section
3x find the race in the spawn path
That is five agents in one click. Each gets its own git worktree (<repo>--fd-<callsign>, or <repo>--fd-<TICKET>-<animal> when the source branch carries a Jira ticket) on its own branch (fd/<callsign> / fd/<TICKET>-<animal>), so they share a repo without standing on each other's edits — isolation is forced for a batch, not offered. The 3x prefix runs a line several times, which is how you race independent attempts at one bug.
Before anything launches the form shows the exact list and count. That preview is the guardrail: there is deliberately no cap on how many agents may be live. It is your machine and your token budget.

Architecture
terminal 1..n : plain `claude` + this plugin's hooks
│ events (http hooks, fail-open) ▲ whispers, blocks, decisions
▼ │ (hook responses)
fleetd — one Bun daemon, loopback by default, SQLite state
▲ │ WebSocket push
└────────── the board (React) ─────────┘
- No wrapper. The plugin's hooks make plain
claude fleet-aware. The first session's SessionStart hook elects and launches the daemon; the port bind is the election. Standalone/Coder installs supervise the same daemon independently of any Claude session.