0
0
via GitHub ยท Posted Jul 26, 2026 ยท 1 min read

ccmux: AI Coding Agent Multiplexer for tmux

epilande/ccmux
Tool

๐Ÿ”ฎ Track all your AI coding agents (Claude Code, Codex, Cursor, ...) in tmux and jump to the one that needs you

165Stars
28Forks
5Open issues
3Watching
TypeScript MIT v1.4.0 Updated 13 minutes ago
View on GitHub

ccmux is a tmux-integrated dashboard for monitoring and managing multiple AI coding agent sessions (Claude Code, Cursor, Codex, etc.) with live state tracking, session grouping, and one-tap navigation. It runs as a background daemon that surfaces idle, working, and waiting agents through an interactive TUI, letting developers switch between agent sessions and approve actions without leaving tmux.

0 comments

README

โ“ Why?

When running multiple AI coding agent sessions across tmux panes, it's hard to keep track of which session is idle, which is waiting for permission, and which pane to switch to. ccmux solves this with a background daemon that monitors session activity and an interactive TUI that shows live session states at a glance.

It works with your existing tmux workflow. You don't change how you launch or run your agents; ccmux discovers what's already running in your panes, so as long as you're in tmux with a supported agent, it just works.

Built-in support for: Claude Code, Codex, Cursor, OpenCode, Pi, Antigravity, Copilot, Gemini CLI, plus custom agent definitions via config.

โœจ Features

  • ๐ŸŽฏ Live Session States: Every agent tracked as idle, working, or waiting (permission / plan approval / question), flagged the moment one needs you
  • ๐Ÿงฉ Multi-Agent: Claude Code, Codex, Cursor, OpenCode, Pi, Antigravity, Copilot, Gemini CLI, plus custom agents via config
  • ๐Ÿ”„ Real-Time: Background daemon streams state changes instantly over SSE, no polling, no refresh
  • ๐Ÿ‘๏ธ Live Preview: Split-pane view of the selected session's pane content
  • โšก Act in Place: Tab into the preview to approve, answer, or type, keys go straight to that pane
  • ๐Ÿ”” Actionable Notifications: Approve, deny, or reply to a waiting agent straight from the desktop notification
  • ๐ŸชŸ Sidebar Mode: Compact always-visible session rail docked beside your working panes
  • ๐Ÿ” Fuzzy Search: Fuzzy-match sessions by project, branch, or path; substring-match any recent prompt, captured pane content, and on-demand live transcripts
  • ๐Ÿ“‚ Session Grouping: Collapsible project groups with reordering and pinning
  • ๐ŸŒฟ Git & PR Aware: Branch and worktree detection, open PRs with live CI and review status
  • ๐ŸŒฑ Worktree Workflow: Spawn or fork sessions into fresh git worktrees, move uncommitted changes out of a dirty checkout, and prune leftovers from the Worktrees panel
  • ๐Ÿ“ Diff Review: Press d to review a session's working-tree diff with hunk, right in the pane, or D for everything the branch changed since it forked
  • ๐Ÿค– Background Agents & Subagents: Claude Code background agents get rows too; running subagents show as agents with a live list in the preview
  • ๐ŸŽ›๏ธ Session Control: Spawn, fork, kill, and restart sessions from the TUI; ccmux invoke for scripted one-shot agent turns
  • ๐Ÿค Session Handoff: Send a session's last response to another agent, from the CLI, the row menu, or agent-to-agent via the bundled relay skill
  • โŒจ๏ธ Keyboard-First, Mouse-Friendly: Vim keys and number jumps, plus click-to-switch and right-click context actions

๐Ÿ“ฆ Installation

Prerequisites

  • tmux with active sessions running AI coding agents
  • git 2.31 or newer (for branch and worktree detection; ccmux still works without it, just without that info)

Homebrew

brew install epilande/tap/ccmux
ccmux setup

From Source

Requires Bun.

git clone https://github.com/epilande/ccmux.git
cd ccmux
bun install
bun link
ccmux setup

ccmux setup installs agent hooks for authoritative session matching. ccmux works without it, but it's recommended; see Session Matching with Hooks. Bare ccmux setup only configures agents whose executable is found on PATH; use ccmux setup --agent <name> to install for a specific agent even if it isn't detected.

Shell Completions

ccmux completion <shell> prints a completion script for zsh, bash, or fish. It completes commands and flags, and asks the running daemon for live values: session ids and %pane ids wherever a command takes one, agent names, invocation ids, config keys and their values. Directory flags such as --cwd fall through to the shell's own path completion.

# zsh (~/.zshrc, after compinit)
eval "$(ccmux completion zsh)"

# bash (~/.bashrc)
eval "$(ccmux completion bash)"

# fish
ccmux completion fish > ~/.config/fish/completions/ccmux.fish

The zsh eval has to come after compinit, since the script registers itself with compdef. A file in fpath sidesteps that and avoids running ccmux at every shell start: ccmux completion zsh > ~/.zfunc/_ccmux with fpath=(~/.zfunc $fpath) before compinit. The Homebrew formula installs completions for all three shells.

๐Ÿš€ Quick Start

  1. Start your AI coding sessions in tmux panes as usual
  2. Launch the picker:
    ccmux
    
  3. Navigate with j/k, press Enter to switch to a session

[!TIP] Bind a tmux key so you can pop ccmux open from anywhere (add to ~/.tmux.conf):

# Prefix + C-p: open ccmux in a centered popup
bind-key C-p display-popup -E -w 80% -h 75% "ccmux"

# Or skip the prefix entirely (Alt+p from any pane)
bind-key -n M-p display-popup -E -w 80% -h 75% "ccmux"

With the binding above, ccmux works out on its own which terminal opened the popup, so picking a session switches that terminal and leaves any other attached client where it was. The picker exits after selection, so the popup closes itself and drops you straight into that pane. (display-popup needs tmux 3.2+.)

Hand ccmux the client explicitly if you attach two terminals to the SAME tmux session, since nothing inside the popup can tell them apart, or if your binding opens the popup on a session other than the one the pressing terminal is looking at:

bind-key C-p run-shell -C 'display-popup -E -w 80% -h 75% "ccmux --client-tty #{client_tty}"'

The run-shell -C wrapper is what makes #{client_tty} expand, since display-popup never expands its own command. The -e "CCMUX_CLIENT_TTY=#{client_tty}" form documented previously still works, on tmux 3.3+.

๐ŸŽฎ Usage

CLI Commands

Command Description
ccmux Launch interactive TUI picker (default)
ccmux picker Launch TUI with options (--preview, --icons <style>)
ccmux picker --persistent Dashboard mode (stay open after switching sessions)
ccmux spawn [agent] Spawn a new agent session in a tmux pane
ccmux invoke [agent] "prompt" Run a single agent turn and write the response to stdout (docs)
ccmux invoke list List active and recently-finished invocations (-j for JSON)
ccmux invoke cancel <id> Cancel a running invocation by id (idempotent)
ccmux invoke result <id> Print an invocation's full captured output (subprocess agents only)
ccmux show List all active sessions
ccmux show --json Output sessions as JSON
ccmux status Show daemon and session overview
ccmux switch <id> Switch tmux client to a session's pane
ccmux review [id] Review a session's diff with hunk (defaults to cwd)
ccmux kill <id> Kill a session's process
ccmux restart <id> Kill and resume a session
ccmux last <session-ref> Print a session's last response (--turns <n>, --json) (docs)
ccmux handoff <from> [to] Hand a session's last response to another session (--turns, --note, --spawn, --agent, --cwd) (docs)
ccmux send <id> <text> Send text to a session's tmux pane (multiline pastes as one message; --no-enter skips submit)
ccmux send <id> --stdin Same, reading the text from stdin instead of argv
ccmux screen [id] Capture pane content
ccmux screen --grep <pattern> Search across all session panes
ccmux dismiss <id> Remove a session from tracking
ccmux worktree list List every worktree of the repos ccmux knows about, plus the one you are in (--repo <path>)
ccmux worktree prune Remove worktrees whose work is finished (--dry-run, --state, --end-idle, --repo <path>)
ccmux daemon start|stop|restart|status Manage the background daemon
ccmux config set <key> <value> Set a preference
ccmux config get <key> Get a single preference value
ccmux config list List all preferences
ccmux config themes List built-in themes (marks the active one)
ccmux setup Install hooks for every supported agent found on PATH (Claude + Codex + Cursor + OpenCode + Pi + omp + Antigravity + Copilot)
ccmux setup --agent <name> Limit install/uninstall/status to specific agent(s); forces install even if not found on PATH
ccmux setup --status Report install state without writing anything
ccmux setup --uninstall Remove hooks (preserves user-owned hook entries)
ccmux debug Diagnose session tracking discrepancies
ccmux completion <shell> Print the completion script for zsh, bash, or fish (install)
ccmux notify [message] Send a notification via the configured backend (bare: test message + diagnostics)
ccmux sidebar Launch narrow sidebar TUI (no preview/footer)
ccmux sidebar --toggle Smart toggle: spawn/kill sidebars in every window across all tmux sessions

The daemon starts automatically the first time you run a ccmux command (picker, show, invoke, etc.). It runs on 127.0.0.1:2269 and provides both a REST API and SSE event stream.

Preview Pane

Press P to split the picker and preview the highlighted session's live pane content side by side. Press Tab to focus the preview and act in place: your keystrokes go straight to that agent's pane, so you can approve a permission, answer a question, or type a follow-up without ever leaving ccmux.

The preview header names the session: the project in bold, then the agent's own summary of the session on agents that publish one (Claude Code, OpenCode, Cursor, oh-my-pi, and Copilot do; on the rest the line is omitted), then the working directory and the branch/version/target metadata.

When the session has agents running, an Agents section lists each one with its runtime. Finished agents drop off the list.

https://github.com/user-attachments/assets/7e0d42b3-4e7b-43b8-8d06-72a2d69dd694

Diff Review with Hunk

hunk is a terminal diff reviewer. With hunk on your PATH, press d in the picker to review the selected session's working-tree diff without leaving ccmux: the picker suspends, hunk diff --watch takes over the pane in the session's repository root, and the picker resumes when hunk exits. The same action is available from the row menu (m, or right-click). If the working tree has no changes, ccmux reports that instead of opening an empty review.

Shift+D reviews the other diff: everything the checkout has changed since it forked, not just what is uncommitted. That is the question worth asking about an agent working in a worktree, which commits as it goes and often has an empty working tree while the branch is the whole point. The base it compares against is whatever ccmux spawn --worktree recorded when it cut the branch, falling back to the merge-base with the repo's default branch for checkouts ccmux did not create. A checkout carrying no commits of its own beyond that base has no fork point to compare against, so D there shows the working tree, the same as d; a main checkout with unpushed commits does have one, and D shows those too.

To send review feedback back to the agent:

  1. Press c in hunk to annotate a line, then Ctrl+S to save the note.
  2. Add any other review notes and quit hunk.
  3. Confirm Send review comments when the picker resumes. ccmux sends all captured notes, including short source snippets, to the agent as one prompt and stays in the picker so you can watch its status.

https://github.com/user-attachments/assets/4b729700-4903-44ff-8f1c-df4bc16b6f67

The offer relies on hunk's session JSON commands (hunk session list / session comment list). With an older hunk the review itself still works; the offer just doesn't appear.

The reviewHandback preference controls what happens when hunk exits:

  • confirm (default) asks before sending the prompt.
  • auto sends and submits the prompt immediately without a dialog.
  • fill pastes the prompt into the agent's composer without submitting it. The text remains there until you jump to the session and submit or edit it; a later send or invoke may find it prepended.

The review also runs from the CLI:

ccmux review          # Review the current directory's repository
ccmux review <id>     # Review a session's repository by id

Install hunk with brew install hunk. The d footer hint and help entry appear only when hunk is detected on PATH at launch.

Sidebar Mode

A compact, always-visible session list that lives alongside your working panes. No preview panel, no footer, just status icons and project names.

ccmux sidebar --toggle                  # Toggle sidebars in all tmux windows
ccmux sidebar --toggle --width 40       # Custom width (default: 30)
ccmux sidebar --toggle --position right # Right side (default: left)
ccmux sidebar --resize --width 30       # Snap every existing sidebar pane to <width>

The smart toggle fills gaps when some windows are missing sidebars, and kills all sidebars when every window already has one. New windows automatically get a sidebar, and sidebars snap back to their configured width when a window is resized.

Configure defaults so --toggle uses your preferred layout:

ccmux config set sidebar.width 40
ccmux config set sidebar.position right

Suggested tmux keybinding (add to ~/.tmux.conf):

bind-key S run-shell "ccmux sidebar --toggle"

Notifications

Desktop notifications on waiting/finished transitions, disabled by default. When a session needs permission, or has a plan waiting for approval, the banner carries Approve / Deny buttons; permission, plan, question, and "finished" notifications also carry an inline Reply field, so you can answer, redirect, or send the next instruction without switching to its pane. Focusing a session's pane clears its notification.

Permission โ†’ Approve / Deny Question โ†’ inline Reply
ccmux config set notifications.enabled true
ccmux notify   # sends a test notification and prints setup diagnostics

Actionable Approve/Deny buttons work for Claude Code, OpenCode, Codex, Cursor, Gemini CLI, Antigravity, Copilot, and oh-my-pi; Pi has no tool-approval pause, so it never raises a waiting notification. Inline Reply on waiting-state notifications (permission, plan, question) is Claude Code only; Reply on finished notifications works for every built-in agent. A reply the agent's composer would misread as a command (e.g. leading ! or /) is refused instead of typed, and the text comes back in a follow-up notification so it isn't lost. Approve/Deny work on macOS and Linux; inline reply needs a notification server that advertises it (always on macOS, varies on Linux).

For OpenCode, one server can host several sessions folded into a single row, so when more than one is waiting at once the buttons are withheld (the keystroke could land on the wrong session's dialog) and the notification is delivered informational-only.

macOS: the buttons, ccmux's own name and icon, per-session grouping, and retraction come from a helper app Homebrew installs alongside ccmux, so brew install epilande/tap/ccmux for the full experience. Source installs fall back to osascript (posts as Script Editor, silenced by Focus / Do Not Disturb, no buttons or reply). macOS never shows a permission dialog for a CLI-launched app, so grant it once by hand: run ccmux notify and follow the printed steps (open the settings deep link, find ccmux, enable Allow notifications, set Alert Style to Persistent), then re-run ccmux notify to confirm.

Linux: dbus grouping, click-to-jump, and Approve/Deny are native (no extra binary); inline reply appears only when the server advertises it. A headless daemon (SSH, systemd) needs DBUS_SESSION_BUS_ADDRESS, plus DISPLAY for the notify-send fallback.

Configure further with ccmux config set notifications.<key> <value>, or edit ~/.config/ccmux/ccmux.json directly:

{
  "notifications": {
    "enabled": true, // default false (opt-in)
    "events": ["waiting", "finished"], // default both
    "sound": "Glass", // false (default) | true (platform default sound) | macOS sound name
    "delayMs": 1000, // debounce for "finished" only; "waiting" always fires immediately
    "backend": "auto", // "auto" | "ccmux-notifier" | "osascript" | "notify-send" | "dbus" | "osc" | "command"
    "command": "ntfy publish agents \"$CCMUX_TITLE: $CCMUX_BODY\"", // used when backend = "command"
  },
}

backend: "auto" picks ccmux-notifier (else osascript) on macOS, and D-Bus (else notify-send) on Linux. command runs your own shell command with CCMUX_* env set (EVENT, SESSION_ID, AGENT, PROJECT, BRANCH, TITLE, SUBTITLE, BODY, PANE), for ntfy, Pushover, and the like. CCMUX_BODY is the complete text (the event line plus any context), so a script reading only it still gets something meaningful; CCMUX_SUBTITLE is the bare event line on its own for structured consumers.

The osc backend delivers notifications through the terminal stream instead of a desktop API, for daemons running on a remote box; see Remote / SSH.

[!NOTE] Approve/Deny only send the mapped keystroke to that session's pane (for Claude, the same key you'd press yourself). Approve on a plan picks "manually approve edits" (edits stay gated), never Claude's auto-accept mode. Reply on a permission or plan notification denies the pending tool/plan and sends your text as the next message (it cancels the prompt first, then types). If the session moved on since the notification fired, the press sends nothing and you get a fresh "state changed" notification instead; dismissing a notification never approves anything.

The keystrokes behind the buttons come from a per-agent notificationActions map, overridable per Custom Agents.

Search Mode

Press / to filter the list as you type. ccmux searches several sources at once and highlights why each row matched:

  • Metadata (project, branch, path) matches fuzzily, so ccmx still finds ccmux.
  • Recent prompts, captured pane content, and live transcripts match by substring, so a content word matches only where it actually appears.

Prompts come from the daemon's in-memory index, which keeps the most recent prompts per session and is tail-bounded after a daemon restart (only recent prompts are re-read from disk). Transcript search closes that gap: it reads each session's transcript file on demand and covers the full session history, including assistant replies (Claude and Codex).

Three toggles control what gets scanned: searchPaneContent, searchPaneLines, and searchTranscript (see Configuration).

Spawning Sessions

Launch new agent sessions directly from the CLI:

ccmux spawn                                                        # Spawn claude (default) in a new tmux window
ccmux spawn codex                                                  # Spawn a specific agent
ccmux spawn --split                                                # Split current pane instead of new window
ccmux spawn --split h                                              # Split left/right ('v' is the stacked default)
ccmux spawn --target %12                                           # Split (or place the window next to) a specific pane
ccmux spawn --detach                                               # Don't switch to the new pane
ccmux spawn --cwd ~/proj                                           # Set working directory
ccmux spawn --resume <id>                                          # Resume an existing session
ccmux spawn --fork <id>                                            # Branch an existing session into a new one
ccmux spawn --prompt "fix the tests"                               # Send an initial prompt
ccmux spawn --model opus                                           # Start the agent on a model (its own flag, e.g. claude --model)
ccmux spawn --worktree --prompt "fix flicker"                      # Spawn into a git worktree (name derived from the prompt)
ccmux spawn --worktree fix-flicker                                 # Spawn into a named worktree, creating it if needed
ccmux spawn --worktree --base develop --prompt "fix flicker"       # Branch the new worktree from develop
ccmux spawn --worktree fix-flicker --with-changes                  # Move this checkout's uncommitted work into it
ccmux spawn --worktree fix-flicker --with-changes --untracked copy # Untracked files land in both
ccmux spawn --fork <id> --worktree                                 # Fork into a fresh worktree off the source's branch
ccmux spawn --pr 154                                               # Check that PR out in a worktree, on its own branch
ccmux spawn --issue 150                                            # New worktree named after the issue, prompt seeded

Split directions use tmux's own vocabulary: h puts the new pane beside the old one, v stacks it below. Run inside tmux, ccmux spawn uses the pane you ran it from, so the new pane or window lands in your session rather than wherever the daemon happens to consider "current". A new window is appended at the end of your session, which leaves every existing window index alone; pass --target <pane-id> to insert one directly after that pane's window instead (tmux renumbers the windows after it), or --target none to let tmux place it.

--target accepts any pane on the server, including one in a different tmux session: ccmux creates the pane there and moves your client over to it, the same jump ccmux switch makes. Pass --detach to leave your view where it is. That jump needs a client of its own to move, so it only happens when you run ccmux spawn from inside the session you are attached to; run from outside tmux, or from a detached session, it creates the pane without switching (use ccmux switch afterwards).

--prompt starts the agent interactively with the prompt already submitted. It is supported for the agents whose interactive-with-prompt invocation ccmux has verified; for anything else (including custom agents) ccmux refuses the spawn rather than guessing a flag, and you can teach it the right shape with promptCommand in your agent config.

--model <name> starts the agent on that model, passed through as the agent's own flag (--model for every built-in, read from each CLI's --help). An agent with no known flag, including a custom one, refuses the spawn; declare modelFlag in its config to teach it. A new window is named after the worktree when the spawn has one (fix-flicker, issue-150-..., pr-154-...) and after the agent otherwise, so a batch of spawns is tellable apart; the name pins tmux's automatic-rename off for that window.

--worktree [name] spawns the agent into a git worktree at <main>/.claude/worktrees/<name>, creating it first if it doesn't exist yet. An explicit name is create-or-open: spawning into the same name again reuses that worktree rather than failing. Without a name, ccmux derives one from --prompt's opening words; a derived name that collides with an existing worktree gets a numeric suffix (-2, -3, ...) instead of reusing it, since two different prompts landing in the same worktree would silently merge unrelated work. --base <ref> sets what the new branch is cut from, defaulting to the main checkout's current branch.

Creating a worktree also adds **/.claude/worktrees/ to the hosting repo's .git/info/exclude (the same line Claude Code writes, and the same file โ€” local to your clone, never .gitignore), so the worktrees don't show up as untracked work in the checkout that hosts them. It is added once, only if git isn't already ignoring that path, and nothing else in the file is touched.

Spawning on a PR or an Issue

--pr <n> resolves the pull request with gh, fetches pull/<n>/head, and spawns the agent into a worktree checked out on the PR's own branch, set up to track it the way gh pr checkout would, including a fork PR, whose branch is pointed at the fork's clone URL so git push updates the PR instead of failing. The worktree is named pr-<n>-<head-ref>, which deliberately never collides with the pr-<n> directories Claude Code creates for its own fetch-only PR checkouts. --base is refused here: the PR's head is the start point. ccmux records origin/<base> as the branch's review base, so d in the picker shows the PR's actual diff.

--issue <n> is an ordinary spawn-from-base worktree named issue-<n>-<title>; --base works as usual.

Both seed the agent's opening prompt with the title and URL, and your own --prompt is appended after it. A PR whose branch is already checked out is opened rather than duplicated; a second --issue of the same number opens the existing issue-<n> worktree the same way. Both refuse rather than guess: a PR that is not open, an issue that is closed, and a same-named local branch that is not that PR (a branch counts as the PR's only when its merge and remote config both already point at it, so a fork PR cannot ride in on a name collision with one of your origin-tracking branches). The remote is compared as a repository, not as text, so a branch you set up yourself with git remote add fork <url> and git checkout -b <branch> fork/<branch> is recognized as the PR's. A local branch that is the PR is fast-forwarded, never force-updated. If it has diverged, ccmux leaves it alone and says so.

ccmux fetches the PR from origin, while gh resolves the number through its own repo selection (gh repo set-default, GH_REPO, a fork clone's upstream). If those name different repositories, ccmux refuses and names both rather than checking out a same-numbered PR from the wrong repo.

Moving Uncommitted Changes

--with-changes relocates the checkout's uncommitted work into the worktree it creates, so the new agent starts on top of it and the checkout you left is clean:

ccmux spawn --worktree fix-flicker --with-changes

Move changes in a session's context menu does the same thing from the picker. It only appears for a row whose checkout is actually dirty, and it opens the new-session dialog already set to move: the destination is locked to a new worktree, an Untracked row appears, and the name and prompt stay editable.

The picker holds any outcome that needs your attention until a keypress: a failure that parked your work in a stash (with the command to get it back), a move that completed but could not drop its own backup entry, a staged/unstaged split it could not preserve, or a spawn that failed after the changes had already moved (naming the worktree they moved into). Only refusals that changed nothing (a name already taken, nothing to move) are a passing toast, since the fields to fix them are still in front of you. The sidebar toasts what a clean move did; the picker jumps straight into the new pane, as it does for every other spawn.

Untracked files move by default (leaving them behind would strand the work you are relocating). --untracked copy puts them in both places, --untracked leave keeps them where they are. Gitignored files are never moved or copied in any mode; worktree.symlinkDirectories and .worktreeinclude cover those.

The move is stash-first: your changes are stashed out of the checkout, the worktree is created, the entry is applied into it, and only then is the entry dropped. If anything fails before the entry is dropped, your checkout is put back as it was, and the stash entry holding your work is reported by sha whether or not the restore succeeded.

Staged and unstaged changes arrive as you left them where git allows it; if the split cannot be preserved, everything still moves and you are told to re-run git add.

ccmux refuses to run at all while a merge, rebase, cherry-pick, revert, or bisect is in progress, and refuses a worktree name that already exists โ€” a move needs a fresh worktree, because rolling one back would take a checkout it did not create.

Forking Sessions

Fork starts a second session that continues an existing conversation's history, in a pane beside the original, and leaves the original running and untouched. (A source with no pane of its own gets a new window instead.)

F in the picker, or Fork in a session's context menu, opens the new-session dialog over the row; Enter on it forks straight away, into a pane beside the source's own. ccmux spawn --fork <session-id> forks the same way but places the result like every other ccmux spawn, relative to the pane you run it from. Either way the new pane is tracked like any other session, with its own row, state and id.

The fork starts in the source's directory by default. --cwd elsewhere is honored, since the fork resumes the source's transcript by path rather than by looking a session id up under the current directory.

--worktree goes one further and creates the destination, so the two sessions edit their own checkouts instead of one. The worktree is named after the branch the source is on (<branch>-fork, numbered -2, -3 if that name is taken) and cut from it, so the fork continues on the history its conversation was written against. Name it yourself with --worktree <name> or pick the ref with --base. --with-changes is refused on a fork: the original session is still running in the checkout those changes would be moved out of.

The picker's version of that is the Where row in the dialog F opens. It starts on This checkout, so an untouched dialog is the plain fork beside the original; move it to New worktree and a Name row appears, previewing the <branch>-fork the daemon would derive and taking one of your own instead. A Source row names the conversation being continued; everything else comes off the row. Where the source is not in a git repository the choice is locked to its own checkout, since there is nowhere for a linked one to go.

Fork needs two things, and the picker hides the action when either is missing: the agent has to declare how it forks (forkCommand), and ccmux has to know which conversation the pane holds. For most agents that knowledge comes from hooks, so run ccmux setup if the action isn't offered. Today Claude Code is the only agent that ships a fork command, because it is the only one whose behavior when resuming a still-running session has been verified. Adding another is one config line once you have checked it yourself (see docs/agent-adapters.md).

Worktrees Panel

After a branch is merged (and auto-deleted on GitHub), three leftovers stay on your machine: the worktree directory, the local branch, and often a tmux pane with a finished agent in it.

W in the picker (or Worktrees on a group header) opens the Worktrees panel: every worktree of every repo in scope, main checkout first, with its branch, ahead/behind, uncommitted counts, open PR, and the agent living in it. Enter jumps to that agent, or starts one in a worktree that has none. Tab widens from the selected row's repo to all of them, y copies a path, and d reviews what the branch changed since it left its base (not just what is uncommitted, which is what d on a session row shows).

The panel has a second view: l switches to Pull Requests, every open PR of the repos in scope, with its branch, author, review state and checks, and the worktree it is already checked out in where there is one. Enter there cuts a worktree from the PR (or jumps to the agent already in it), o opens it on GitHub, and h goes back to the worktrees. The two axes are independent: Tab still scopes either view to one repo or all of them, and the tab line under the title names both views with the live PR count.

N in the picker (or n inside the Worktrees panel) opens the source picker: every open pull request and every open issue of the repos in scope, in one list, with the worktree that already holds each one where there is one. / filters across both at once, so a word you remember finds it whether it was filed as a PR or as an issue, and the section counts follow what you type. Enter starts work on the row: it cuts a worktree from a PR's head, or one named after an issue and seeded with it, and where a checkout already exists it goes there instead of starting a second agent in the same tree.

https://github.com/user-attachments/assets/cc25199b-f563-4cda-8b59-6e95c449a94a

The panel loads in two passes: the list paints immediately from local git state, then the prune classification (which fetches and asks GitHub) merges in and sinks the finished worktrees to the bottom of their group. Those, and only those, become selectable for removal, showing why each one is removable. Space selects a row, and <kbd

Comments (0)

Sign in to join the discussion.

No comments yet

Be the first to share your take.