A cmux-like manager and control plane for running many concurrent Claude Code sessions in one Emacs.
cc-butler turns claude-code-ide into a multi-session cockpit: a sticky list of
every live session, a butler/worker orchestration layer where one designated
session drives the others, a per-session document panel, and a butler-owned
document repository that keeps a live dashboard and an append-only log.
The original motivation: when you run several Claude Code agents at once, their output scrolls away and the overall state of the work becomes impossible to hold in your head. cc-butler makes the fleet legible — and lets one butler session (which you can drive from your phone) coordinate the rest.
(Mocked content — no real project or session data.)
The session list (left) beside a worker’s terminal (right). household.png
below is the roles diagram; the rest illustrate individual features.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
- Session list (
M-x cc-butler) — a sticky left side window listing every live session as a readable multi-line block (title / current activity / status / branch + PR). Navigating previews the session’s terminal. - Per-session metadata — a running Claude sets its own title/status via the
set_session_infoMCP tool, so you can track many sessions at a glance. - Topic workspaces —
cc-butler-new-topic(N) scaffolds a workspace (clones + markers) and launches a session;cc-butler-close-topic(K) tears one down safely (it refuses to delete unless every git clone has no local-only commits, a clean tree, and no stashes — checked with realgit). The package ships one built-in template,default(a repo-free scaffold — no cloning, just a workspace dir), plus anarbitraryescape hatch (pick any directory, no scaffolding); register your own real-repo templates withcc-butler-define-project-templatein your own config (see its docstring). - Butler / worker control plane — a designated butler session drives the
workers through MCP tools (
list_claude_sessions,read_session_output,send_to_session) and receives their events (report_to_steward→pending_events). You remote-control one session; it becomes the situation room for the rest. - Document panel — a running Claude opens a PR, issue, CI run, or file into a
right-hand panel beside its terminal (
show_document), shown as a tab line; the set is remembered per session. Read-only with explicit actions (comment / browse). You can also open one yourself witho(orC-c d ofrom anywhere) — see The document panel below; the panel stays invisible until at least one document has been opened for that session, so don’t mistake an empty panel for a missing feature. - Inbox — decisions/notes the butler or steward escalates land in a
reviewable list (
M-x cc-butler-inbox,iin the session list); see The inbox below for what populates it. - Butler document repository — the butler keeps a regenerated
dashboard.org(sessions table auto-built from live state + overview + open decisions) and an append-only daily log, updated viabutler_log/butler_dashboardand auto-captured worker events.
- Emacs 29.1 or newer.
- claude-code-ide.el — the Claude Code IDE integration for Emacs (the session backend and MCP bridge this package builds on).
- ghostel — the terminal backend that claude-code-ide drives; cc-butler reads
its live terminal state (OSC titles, screen capture) and rides its
notification callback. Not a formal dependency of either package (neither
declares it in
Package-Requires), so on a fresh install it is easy to skip — but without it, sessions still launch under claude-code-ide’s defaultvtermbackend, just with cc-butler’s OSC-title tracking, terminal resize, and screen-refresh features silently disabled. Install it separately, then:(setq claude-code-ide-terminal-backend 'ghostel)
The Claude Code CLI itself must be installed and on claude-code-ide-cli-path
(default "claude", resolved via PATH). If you use the ghostel backend,
set this to an absolute, tilde-free path instead of relying on the default —
ghostel spawns the process directly via execvp with no shell, so a literal
"~/..." is never expanded and the process dies instantly, surfacing as a
bare “Invalid buffer” error with no indication why:
(setq claude-code-ide-cli-path (executable-find "claude"))Run M-x cc-butler-doctor (h in the session list) any time to check these —
useful right after a fresh install, since none of the above is verified or
installed by (require 'cc-butler) itself.
cc-butler is not on a package archive yet, so it installs straight from Git.
One wrinkle to know first: its claude-code-ide dependency is also GitHub-only
(not on MELPA/GNU ELPA), so a package manager that tries to satisfy it from an
archive will fail. Install ~claude-code-ide~ first, then cc-butler — the
recipes below do this in order. (The other dependency, hydra, is on MELPA and
resolves automatically.)
Emacs 30 ships a :vc keyword in use-package, so no third-party package
manager is needed. Make sure MELPA is registered (for hydra):
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
;; 1. GitHub-only dependency first
(use-package claude-code-ide
:vc (:url "https://github.com/manzaltu/claude-code-ide.el" :rev :newest))
;; 2. then cc-butler
(use-package cc-butler
:vc (:url "https://github.com/toracle/cc-butler" :rev :newest)
:after claude-code-ide)Upgrade later with M-x package-vc-upgrade RET cc-butler RET.
The :vc keyword is Emacs 30+. On 29.x, install with package-vc-install (run
once), then configure with plain use-package:
(unless (package-installed-p 'claude-code-ide)
(package-vc-install "https://github.com/manzaltu/claude-code-ide.el"))
(unless (package-installed-p 'cc-butler)
(package-vc-install "https://github.com/toracle/cc-butler"))
(use-package cc-butler
:after claude-code-ide)straight reads Package-Requires and pulls hydra from MELPA for you:
(use-package claude-code-ide
:straight (:host github :repo "manzaltu/claude-code-ide.el"))
(use-package cc-butler
:straight (:host github :repo "toracle/cc-butler")
:after claude-code-ide)Or clone both repos and put them on your load-path:
(add-to-list 'load-path "/path/to/claude-code-ide.el")
(add-to-list 'load-path "/path/to/cc-butler")
(require 'cc-butler)After any of these, run M-x cc-butler-doctor to check for fresh-install gaps
(CLI path, ghostel backend, etc.) — see Requirements above.
cc-butler installs are package-vc-managed — a pinned git checkout, not a
registry package. Merging a fix to ~main~ on GitHub does not reach any
running instance by itself. Each machine hosting a fleet must explicitly
upgrade and recompile, and a live Emacs process keeps executing the old code
until it does. Never treat “merged” as “deployed” — verify by observation.
On each machine:
- Upgrade the install:
(package-vc-upgrade (car (alist-get 'cc-butler package-alist)))
(or interactively:
M-x package-vc-upgrade RET cc-butler RET) - Verify the files on disk actually moved — don’t infer this from the
command not erroring:
git -C ~/.emacs.d/elpa/cc-butler log -1 --onelineCompare that SHA against the expected merge commit /
origin/main. Only a match confirms the install itself is current. - If a live Emacs session was already running cc-butler before step 1, its
already-loaded function definitions stay on the old code in memory even
after the files on disk are updated. Restart Emacs, or explicitly reload
the changed file(s) (
M-x load-file), so the running process actually executes the new code. Step 2’s SHA only tells you the disk is current — it does not tell you the running process picked it up; check that separately (e.g. inspect the live behavior, orsymbol-file/ re-eval the function and compare against the source).
emacs -Q --batch -l tests/run-tests.elThat script’s load-path is deliberately ordered so an elpa-installed copy of cc-butler doesn’t shadow the working tree under test — see its own header comment for why that matters.
On at least one machine running these tests (confirmed 2026-08-15), the
emacs found on PATH fails outright:
$ emacs --batch --eval '(princ emacs-version)'
exec: /opt/homebrew/Cellar/emacs-plus@30/30.2/Emacs.app/Contents/MacOS/Emacs: No such file or directoryIt is a Homebrew shim exec-ing an app-bundle path that no longer exists on
that machine. Run the same command above to check your own machine — if it
fails the same way, use the working binary confirmed for this repo’s tests
(Emacs 30.2) directly instead of emacs:
/Applications/Emacs.app/Contents/MacOS/Emacs -Q --batch -l tests/run-tests.elThe root cause has not been investigated (a moved or reinstalled app bundle
is a guess, not a confirmed cause). Scope: this only affects CLI batch runs
(tests, this troubleshooting section) — a live/GUI Emacs process already
running cc-butler is unaffected. This is a fact about that one machine,
not about the repo or a universal cc-butler requirement; a machine with a
working emacs on PATH has nothing to do here.
M-x cc-butler opens the session manager. In the list buffer, ? opens a
discoverable Hydra menu of everything below (mirroring the same ? → menu
pattern used by the decision reader) — press it any time you forget a key:
| Key | Action |
|---|---|
n~/~p | move + preview a session |
RET | select the session’s terminal |
SPC | preview without switching |
B | start (or focus) the dedicated butler session |
S | start (or focus) the dedicated steward session |
N | create a topic workspace |
K | close a topic safely (vet → kill → delete) |
b | designate the session at point as the butler |
i | open the inbox (see The inbox) |
d~/~o~/~v~/~D | doc-panel toggle / open / reopen / remove (see below) |
c | start a plain session (no scaffolding) |
l | show the cc-butler log |
g | refresh |
h | cc-butler-doctor — check for fresh-install gaps |
? | open this Hydra menu |
q | quit the session manager |
A running Claude drives its own panel and reports to the butler through the MCP tools listed above.
A per-session right-hand panel for PRs, issues, CI runs, and files. It stays invisible until at least one document has been opened for that session — there is no persistent empty-state placeholder, so a session with nothing opened yet looks identical to the panel not existing at all. Two ways to populate it:
- Automatically: a running Claude calls the
show_documentMCP tool. - Manually: press
oin the session list (orC-c d ofrom anywhere in Emacs) to open a document into the session at point.
Once at least one document is open, further keys apply: d toggles the panel
visible/hidden, ]~/~[ move between open documents, v reopens the
last-closed one, and D removes the current one from the set.
M-x cc-butler-inbox (i in the session list) lists decision/note documents
the butler or steward has escalated for a human to review and answer — not
the agent-to-agent messaging worker sessions use internally (ask_worker /
check_inbox / report_to_steward, which self-initialize and need no setup).
The human inbox only starts filling once you have actually launched a butler
and/or steward session at least once (B / S) — those launches write the
hooks that auto-drain escalated decisions into it. An empty inbox on a fresh
install usually just means neither has been started yet, not that anything is
broken.
M-x cc-butler-start-butler (B) launches a dedicated butler session in
cc-butler-home (default ~/.emacs.d/cc-butler/butler), scaffolding a .projectile
marker and a role CLAUDE.md there on first run, and designating it the butler.
Point cc-butler-home at an existing directory to reuse it. (b still lets you
mark any session the butler by hand.)
M-x cc-butler-start-steward (S) launches a dedicated steward session in
cc-butler-steward-home (default ~/.emacs.d/cc-butler/steward), the same way.
Once running, the worker firehose (nudges + report_to_steward) routes to the
steward instead of the butler (split mode) — the butler then only ever sees
decisions the steward escalates to it (escalate_to_butler /
pending_decisions), never the raw worker chatter.
MIT — see LICENSE. Copyright (c) 2026 Jeongsoo Park.






