Skip to content

cmux control

Roy Padina edited this page Aug 30, 2026 · 2 revisions

cmux-control

You run Claude inside cmux. cmux has a notification centre, a status pill and a progress bar; Claude Code, running inside that terminal, uses none of them. So the only way to know a twenty-minute agent run finished is to keep looking at it.

This plugin closes that gap, and teaches Claude the cmux CLI properly on top.

Setup

/plugin marketplace add roypadina/padina-claude-code-plugins
/plugin install cmux-control@padina

Requires macOS and the cmux app with its CLI on PATH (/Applications/cmux.app/Contents/Resources/bin/cmux). Everything else degrades rather than breaks:

jq Strongly recommended. Without it the notifications lose their body text and the subtitle falls back to $PWD instead of the payload's cwd.
git Not required. Outside a repo the subtitle is just the directory's own name.
Python 3 /cmux-sessions only. Nothing else uses it.

Both hooks are guarded on command -v cmux and on CMUX_WORKSPACE_ID being set. Outside cmux, or without the CLI, they exit 0 without a sound — no errors, no output, nothing in your transcript.

What you get

Feature Mechanism
Notification when a turn finishes Stop hook
Notification when a subagent finishes SubagentStop hook
Sidebar progress + status pill skill guidance — Claude's judgement, not a hook
/cmux-sessions — session inventory and recovery slash command
The whole cmux CLI, for Claude skill

Notifications

Stop and SubagentStop are the purpose-built events, not a PostToolUse matcher on Task. Their payloads carry last_assistant_message, stop_hook_active, background_tasks, and for subagents agent_type — so the notification says something worth reading:

Claude finished
myapp · feature/RD-12851 · 2 background tasks
Fixed the null-deref in the checkout handler. Added a regression test; both suites pass.

Subagent finished · Explore
myapp · feature/RD-12851
Found 3 call sites in src/api.ts

The body is the closing message, whitespace-collapsed and cut to 180 characters on a codepoint boundary, so a multi-byte character is never sliced in half.

The Stop hook respects stop_hook_active (so it never fires again on a continuation) and never emits decision: "block". It notifies and exits 0. It cannot keep a session running.

Set CMUX_CONTROL_QUIET=1 in the environment to turn the notifications off.

cmux's own Claude wrapper sets CMUX_SUPPRESS_SUBAGENT_NOTIFICATIONS=1 for its own notifier. This plugin deliberately does not read that variable — its semantics are undocumented, and installing this plugin is itself the opt-in. CMUX_CONTROL_QUIET is the switch that belongs to you.

The repo · branch subtitle

Derived from git, deterministically, not guessed:

Situation Subtitle
~/Code/myapp on feature/RD-12851 myapp · feature/RD-12851
A subdirectory of that repo myapp · feature/RD-12851
A linked worktree at ~/wt/topic myapp · wt/topic — the main repo's name
Detached HEAD myapp · @a1b2c3d
Not a git repo the directory's own name

Branch names keep their slashes. A worktree reports the main repository via --git-common-dir, because a worktree directory is usually named after its branch and you would otherwise read the branch twice.

Tab and workspace naming — removed in 0.2.0

Versions up to 0.1.0 shipped a SessionStart hook that renamed the horizontal tab and the sidebar entry to repo · branch. It is gone.

The reason is cmux's own opt-in automation.workspaceAutoNaming setting, whose settings copy says, verbatim:

"When enabled, cmux summarizes supported agent sessions into short workspace and tab names using each agent's own binary, refreshed as the topic shifts. Manual renames always win and stop auto-naming for that workspace or tab. Uses your agent account for the short summarization calls."

A rename-tab / workspace-action rename counts as a manual rename. So the hook did not merely set a name — it permanently disabled cmux's naming for that workspace, on every session start, without asking. That decision belongs to cmux and to you.

Names are now yours. Leave them, set them by hand, or turn cmux's auto-naming on and let it summarise the session:

cmux rename-tab "release prep"                                    # horizontal tab
cmux workspace-action --action rename --title "release prep"      # sidebar entry

The skill tells Claude not to rename a tab or workspace on its own initiative — the same restraint it applies to your cmux todo list.

Sidebar progress — the honest disclaimer

This one is not hook-enforced. No hook knows how far through a task Claude is, so there is nothing to wire it to. What the plugin ships is a set of rules in the skill: use set-progress / set-status only for genuinely long multi-step work, at most one call per completed step, and always clear both when the work ends — including when it fails.

Claude follows guidance well but not perfectly. If you want the pill gone:

cmux clear-progress && cmux clear-status cmux_control

When Claude does write a pill, the skill tells it to use the status key cmux_control and never claude_code — cmux's own Claude wrapper owns that key and writing to it would stomp the app's own state. No script in this plugin ever calls set-status or set-progress: every pill you see came from Claude following the skill, which is why this is listed as guidance and not as a feature.

/cmux-sessions

cmux has no detached server: when the app quits, every terminal child process dies, Claude Code included. On relaunch cmux replays each pane's saved resume binding and most sessions come back — most, not all. A workspace can be dropped from the restore, or a pane can come back as a bare shell.

/cmux-sessions                  inventory every live Claude pane, with its resume command
/cmux-sessions check            MISSING workspaces, DEAD panes, transcript warnings
/cmux-sessions restore --dry-run
/cmux-sessions restore

It reads cmux's own state file. Multiple Claude Code profiles are discovered rather than assumed: CLAUDE_CONFIG_DIR is honoured, ~/.claude* directories are scanned, and profiles are deduped by resolved transcript path — so if you symlink projects/ between profiles nothing is counted twice. One profile is the normal case and needs no setup.

The skill

One skill covering the whole cmux CLI: topology and handles, launching agents in panes, browser automation, notifications and sidebar state, cmux.json layouts, and session recovery — plus four reference files loaded on demand (cli-reference, agents, browser, custom-commands).

Verified against cmux 0.64.22, with the traps that cost real time written down: focus-surface and list-surfaces do not exist; --surface resolves inside the caller's workspace, so cross-workspace calls need --workspace (the failure is a misleading invalid_params: Surface is not a terminal); close-surface reports the surface focused after the close; list-panes prefixes the focused row with a literal *; and identify gives you both .caller and .focused, which are frequently different.

It also carries cmux's own guardrail about cmux todo, verbatim: "this checklist belongs to the user. Do not add, edit, complete, remove, or replace items on your own initiative."

What is enforced vs what is guidance

Hook-enforced — happens whether or not Claude cooperates turn-finished notification, subagent-finished notification
Skill guidance — Claude's judgement, usually right, not guaranteed sidebar progress and status pills, restraint about naming and about your todo list
On request /cmux-sessions and everything else in the skill

Checking it works

scripts/selfcheck.sh

Asserts the repo · branch derivation against real throwaway repos — plain directory, slashed branch, subdirectory, detached HEAD, linked worktree, bare repo — then runs the session script's own assertions. No framework; it prints a line per check.

Credits

The idea of wiring cmux to Claude Code hooks comes from hopchouinard/cmux-plugin (MIT, Patrick Chouinard). This is an independent reimplementation — no code was copied — whose notifications say what finished, and where, instead of "Agent finished — check results".

Clone this wiki locally