Skip to content

About

One Claude Code session per git worktree, each in its own cmux workspace: cw launcher, state hooks, sidebar, installer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cmux-claude

One Claude Code session per git worktree, each in its own cmux workspace, with a sidebar that shows what every session is doing.

~/projects/wz $ cw perf                 # workspace "perf", worktree .claude/worktrees/perf, session "perf"
~/projects/wz $ cw perf --resume        # resume that session
~/projects/wz $ c docs                  # same, but on the main checkout (no worktree)
~/projects/wz $ cw perf --teams         # agent-teams mode: named teammates open as split panes

The cmux workspace, the git worktree and the Claude session all carry the same name, so a name is enough to find any of the three. The repository is the one the shell is in, or $CW_REPO (default ~/projects/wz) outside any repository, or --repo <dir>. The name of a worktree never picks the repository: a worktree of the same name in another repo is only mentioned in the refusal of a --resume that has nothing to resume.

What is in here

Path Installed to Purpose
cmux/cw.bash ~/.config/cmux/ the cw and c shell functions, repo resolution, tab completion
cmux/cw-launch.sh ~/.config/cmux/ starts claude through cmux's claude wrapper so the session is tracked
cmux/cw-state-hook.sh ~/.config/cmux/ Claude Code hook: stamps working / question / done / idle into the workspace description
cmux/cw-colors-hook.sh, cmux/homebrew-colors.sh ~/.config/cmux/ paint the pane in Terminal.app's Homebrew colours, also after a cmux relaunch
cmux/sidebars/workspaces.swift ~/.config/cmux/sidebars/ custom sidebar: compact cards coloured by agent state, pinned block on top
cmux/teams-bin/claude, cmux/teams-bin/tmux ~/.config/cmux/teams-bin/ agent-teams mode only: give claude the tmux environment cmux's compat layer expects
patches/claude-settings.hooks.json merged into ~/.claude/settings.json the hook registrations for cw-state-hook.sh and cw-colors-hook.sh
patches/bash_profile.snippet block in ~/.bash_profile sources cw.bash; puts teams-bin on PATH in --teams workspaces
patches/cmux.patch.jsonc block in ~/.config/cmux/cmux.json pins the cmux settings the tooling assumes, and puts the socket in password mode
install.sh, uninstall.sh copy and patch; remove and unpatch. Both take --dry-run

Install

Requirements: macOS, cmux 0.64, Claude Code, bash, jq (for the settings.json merge).

git clone https://github.com/KitStream/cmux-claude.git
cd cmux-claude
./install.sh --dry-run      # shows every file it would copy or patch
./install.sh
source ~/.bash_profile      # in every terminal that is already open

Files are copied, not symlinked; re-run install.sh after a pull. Existing files that differ are backed up beside themselves as <name>.bak-<timestamp>. The three files that already exist on a working machine are patched, not replaced, and every patch is idempotent:

  • ~/.config/cmux/cmux.json is JSONC, so jq cannot touch it. The installer inserts the contents of patches/cmux.patch.jsonc between two marker comments before "schemaVersion" and replaces the block on later runs. If one of the patched keys is already uncommented elsewhere in the file it refuses and asks you to merge by hand, because cmux would otherwise see a duplicate key.
  • ~/.claude/settings.json gets the hook groups from patches/claude-settings.hooks.json appended per event, skipping any group whose command is already registered. Everything else in the file is left alone.
  • ~/.bash_profile gets the snippet between two marker lines, appended at the end.

The sidebar is chosen in cmux's Settings store, not in cmux.json: the installer runs cmux sidebar select workspaces when cmux answers, and otherwise leaves a marker so that the first cw does it.

cw runs from any terminal, and starts cmux when it is not running. cmux's socket by default (cmuxOnly) only accepts processes started from a cmux terminal, and it has no per-program allowlist, so the installer switches it to password mode (automation.socketControlMode). The generated password is kept in ~/.config/cmux/socket-password (mode 600); cw presents it through CMUX_SOCKET_PASSWORD for its own calls only. Any process running as you can read that file and then drive cmux, which is weaker than cmuxOnly; on a shared machine, think twice. cmux does not leave socketPassword in cmux.json: at its next launch or config reload it moves the value to its own store and rewrites the file as plain JSON, comments and markers gone. The installer recognises that state and restores the block (without the password line, which keeps the file stable from then on), so after the very first launch of cmux run ./install.sh once more. While cmux holds a password it does not take a different one from cmux.json, which is why uninstall.sh leaves socket-password where it is.

cw is a shell function. A terminal opened before an install or an edit keeps the old function until it runs source ~/.bash_profile. This has bitten twice; the installer says so.

How it works, and why it is shaped like this

Claude goes through cmux's wrapper. cmux 0.64 injects a per-surface claude shim that adds --session-id and its status hooks. That is what gives the sidebar a working / idle / needs-input state and restores sessions after a relaunch. cmux claude-teams execs the real binary and loses all of it, so cw-launch.sh calls plain claude and lets PATH resolve it to the shim.

Claude is started from the repo root with -w <name>, never from inside the worktree. The root project directory is where Claude files, and looks for, the worktree's sessions. A bare cw perf --resume gets perf as the search term because the worktree-scoped picker lists only the worktree's own project directory and comes up empty for every session cw started.

State comes from two places. The terminal title carries a spinner while Claude works and ✳ while it waits; that is live even without hooks. Only a question cannot be told from a finished turn that way, so cw-state-hook.sh stamps claude:question into the workspace description from the AskUserQuestion, ExitPlanMode and permission-prompt hooks, and the sidebar consults the description for that alone. A stale stamp therefore cannot keep a card blue. Teammate sessions are recognised by --agent-id in their argv and do not stamp.

The sidebar reads titles, not agents. cmux 0.64 hands agents to custom sidebars as nil. The interpreter is also narrow: optional fields only through if let, contains rather than hasPrefix, filter rather than loops with early return, .padding(n) only. The file's header lists what was found not to evaluate.

Agent-teams mode is opt-in (--teams). With CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 on the workspace, cw-launch.sh adds --teammate-mode auto and a system prompt that makes teams spawn as named teammates, and teams-bin/claude sets TMUX/TMUX_PANE for the claude process only, asking cmux's tmux-compat layer for the ids it assigned to the surface (a made-up pane id makes Claude refuse with "Could not determine current tmux pane/window"). teams-bin/tmux forwards every tmux call to cmux __tmux-compat and splices the parent's PATH into the pane command, because compat-layer panes start from a bare /bin/sh without node, homebrew or ~/bin. TMUX must not be in the shell's own environment: it flips cmux's shell integration into a tmux mode that swallows Claude's title updates and made the wrapper skip its hooks.

It is opt-in because the pane path is where the open bugs are. In day-to-day use the teammate panes came up blank, took most of the window width, and never showed idle. Upstream:

Plain cw sessions run subagents in-process, which is the fallback those issues converge on.

Uninstall

./uninstall.sh --dry-run
./uninstall.sh

Removes the copied files, the marked blocks in cmux.json and ~/.bash_profile (plus any hand-installed cw lines), and the hook entries in ~/.claude/settings.json whose command runs a file under ~/.config/cmux/. Groups and events left empty go with them; nothing else is touched. Backups are written beside each edited file. The sidebar choice is not in any file: pick the built-in sidebar again from the sidebar toggle's right-click menu.

About

One Claude Code session per git worktree, each in its own cmux workspace: cw launcher, state hooks, sidebar, installer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages