Skip to content

max-listov/ccmux

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ccmux

Persistent, self-healing Claude Code sessions in tmux — across a fleet of machines.

A single daemon per machine keeps a fleet of long-running agent sessions alive in tmux: it heals crashed ones, brings them back on reboot, and resumes the same conversation by a pinned uuid. Sessions are full interactive claude processes (your subscription, Remote Control, slash-commands, statusline) — ccmux supervises them, it does not reimplement them.

┌─ daemon (launchd/systemd) ─ heals every 30s, self-updates ─┐
│   tmux: cc-api   cc-web   cc-infra   …   (each = `ccmux _run` → claude, auto-restart)
└────────────────────────────────────────────────────────────┘
        ▲ ccmux list / new / attach / send / restart …          ▲ interactive TUI (bare `ccmux`)

Install

One command — installs bun if missing, downloads the latest verified bundle, drops a ccmux shim on your PATH, and starts a self-updating daemon:

curl -fsSL https://github.com/max-listov/ccmux/releases/latest/download/install.sh | bash

Set the boot label / RC prefix with CCMUX_RC_PREFIX=prod (default local). Re-running is safe — it just refreshes to the latest release. Requires macOS (launchd) or Linux (systemd) and tmux.

Use

ccmux                      # interactive fleet TUI (add -f for fullscreen)
ccmux list                 # managed sessions + live status/uptime
ccmux new cc-api ~/code/api   # create + start a session (pins a fresh uuid)
ccmux send cc-api '/compact'  # type into a session (text or a /slash command)
ccmux restart cc-api       # bounce it (survives killing the caller)
ccmux stop|start|rm cc-api # lifecycle (rm keeps the jsonl history)
ccmux transcript cc-api --json --tail 50   # conversation history as JSON
ccmux doctor               # health check: bins, config, daemon
ccmux help                 # full command list

Attach to a session like any tmux pane: tmux attach -t cc-api (detach with Ctrl-b d), or press Enter on it in the TUI.

Adopt an external session

A claude you started by hand (outside ccmux) shows up in the TUI under external. Adopt it to let the daemon manage it:

ccmux adopt <uuid> --fork       # safe copy under a new uuid (original untouched)
ccmux adopt <uuid> --takeover   # take over the original (kills the live writer)

How it works

  • One daemon per machine (launchd com.<prefix>.ccmux / systemd ccmux.service) heals the fleet every 30s and starts it on boot. It runs the prod bundle, not your source.
  • Each session is a tmux session whose foreground process is ccmux _run <name> — a tiny supervisor loop that launches claude and relaunches it on crash (exponential backoff). So an agent crash just comes back; killing a session is the only way to stop it.
  • Deterministic resume: every session pins a fixed uuid (--session-id first, --resume after) → no resume-picker, no forked conversation.
  • jsonl is the source of truth for the conversation (transcript, tokens, "where it stopped"); the pane is scraped only for live status.

Updates

Both paths share one safe core: download → sha256-verify against the manifest → preflight (bun <candidate> version must load and report the right version) → atomic swap of the prod bundle (.bak kept) → bounce the daemon. Sessions survive the bounce (tmux is independent of the daemon); each picks up the new code on its next restart. A boot-guard reverts to .bak if a bad bundle crash-loops the daemon.

ccmux update             # update now to the latest published release
ccmux update --check     # is there a newer version?
ccmux update --rollback  # revert to the previous bundle (.bak)

With autoUpdate on (wired at install via --release-url), the daemon checks every updateCheckInterval seconds (default 300) and applies a newer release on its own — hands-off across the whole fleet.

Develop

ccmux is a Bun + TypeScript app; the TUI is Ink (React → terminal).

bun install
bun run dev            # run the CLI/TUI from source (this is `ccmux-dev`)
bun run smoke          # headless TUI e2e in a throwaway tmux pane
bun test               # tests
bun run typecheck      # tsc --noEmit

The dev source and the prod daemon are decoupled — editing source never touches the running prod bundle. See docs/architecture/ for the TUI, IO/perf model, and dev flow.

Build & release

The release tooling lives in the source checkout only — clients ship a single bundle, no repo:

bun run stage            # build → ~/.ccmux/staged/ccmux.js, then `ccmux update` to test locally
bun run release:publish "notes"   # build → sha256 → GitHub Release vX.Y.Z (3 assets, atomic)

A release is a tag vX.Y.Z with three assets: the ccmux.js bundle, a release.json manifest (version + sha256 + versioned bundle url), and install.sh. Tags are immutable — bump the version in package.json to cut a new one. The fleet tracks releases/latest/download/release.json.

License

MIT © ccmux contributors

About

Persistent, self-healing Claude Code sessions in tmux — deterministic resume, fleet self-update.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors