cmux setup

My terminal of choice is now cmux. Here are my setup notes – how I run agent teams, and how I get my Claude/Codex sessions to come back after a reboot.

Contents

Agent Teams

My model: a workspace per team, either Claude Teams or OMX.

Here’s my cmux config (~/.config/cmux/cmux.json):

{
  "$schema": "https://raw.githubusercontent.com/manaflow-ai/cmux/main/web/data/cmux.schema.json",
  "schemaVersion": 1,

  "terminal": {
    "autoResumeAgentSessions": true
  },

  "actions": {
    "agents.openOMX": {
      "type": "workspaceCommand",
      "title": "Open OMX",
      "subtitle": "Start Oh My Codex in its own workspace",
      "commandName": "OMX"
    },

    "agents.openClaudeTeams": {
      "type": "workspaceCommand",
      "title": "Open Claude Teams",
      "subtitle": "Start Claude Teams in its own workspace",
      "commandName": "Claude Teams"
    }
  },

  "commands": [
    {
      "name": "OMX",
      "description": "Start interactive Oh My Codex in its own cmux workspace",
      "keywords": ["omx", "codex", "agents"],
      "restart": "ignore",
      "workspace": {
        "name": "OMX",
        "cwd": ".",
        "layout": {
          "pane": {
            "surfaces": [
              {
                "type": "terminal",
                "name": "OMX",
                "command": "bash -lc 'export PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\"; exec cmux omx'",
                "focus": true
              }
            ]
          }
        }
      }
    },

    {
      "name": "Claude Teams",
      "description": "Start Claude Code Teams in its own cmux workspace",
      "keywords": ["claude", "teams", "agents"],
      "restart": "ignore",
      "workspace": {
        "name": "Claude Teams",
        "cwd": ".",
        "layout": {
          "pane": {
            "surfaces": [
              {
                "type": "terminal",
                "name": "Claude Teams",
                "command": "bash -lc 'export PATH=\"/opt/homebrew/bin:/usr/local/bin:$PATH\"; exec cmux claude-teams'",
                "focus": true
              }
            ]
          }
        }
      }
    }
  ]
}

After saving, run (right inside cmux):

cmux reload-config

Then open the Command Palette with Cmd+Shift+P and you can launch OMX or Claude Teams.

Surviving a reboot

The goal: after a restart, the agent panes come back on their existing conversations, not as fresh sessions.

This is the setup that currently does that for me (cmux 0.64.15, macOS 15, Apple Silicon):

  1. autoResumeAgentSessions – already true in the config above. It tells cmux to re-run each pane’s saved resume command when cmux reopens. (It is not a boot daemon; it only acts once cmux is running again.)
  2. Run cmux hooks setup once.
  3. Add cmux as a Login Item so it relaunches at login: System Settings > General > Login Items & Extensions > Open at Login > + > /Applications/cmux.app.
  4. Keep launches argument-free. cmux only restores on a no-argument launch (a Login Item / Spotlight / Dock launch all qualify). Don’t set CMUX_DISABLE_SESSION_RESTORE=1 – and watch out if you sync env vars into launchctl.
  5. Be on cmux 0.64.15 or newer – that’s the version where reboot resume started working for me.

You do not need macOS’s “Reopen windows when logging back in” – mine was off (the box was unchecked) during the reboot where everything resumed.

With all of that, after a reboot my agent panes came back resumed – 15 of 15 in my last test, each on its real prior conversation.

An honest caveat. I have not fully isolated which single piece is load-bearing, and there is an oddity: cmux records a per-pane wasAgentRunning flag that was false for every pane in the snapshot, yet the sessions still resumed. Best current read: the resume comes from cmux’s own restore in 0.64.15 plus the Login Item relaunch – not from any macOS window-reopen feature (that was off). A confirming reboot is still on my list. Full trail in the appendix.

Fallback: reopen everything yourself

A forced/hard reboot skips macOS state restoration, and you might leave the box unchecked. For those cases the conversation ids are still saved in cmux’s session snapshot, so you can re-open them yourself. This script reads the snapshot live (no hard-coded ids, so it is safe to share) and opens each saved agent in its own workspace:

#!/usr/bin/env bash
# resume-cmux-agents.sh [list|all|N]
set -u
CMUX="${CMUX_BUNDLED_CLI_PATH:-/Applications/cmux.app/Contents/Resources/bin/cmux}"
mapfile_cmds() {
  python3 - <<'PY'
import json, os
d=json.load(open(os.path.expanduser('~/Library/Application Support/cmux/session-com.cmuxterm.app.json')))
for w in d.get('windows',[]):
  for ws in w.get('tabManager',{}).get('workspaces',[]):
    for pn in ws.get('panels',[]):
      rb=(pn.get('terminal') or {}).get('resumeBinding')
      if rb and rb.get('command'):
        print((pn.get('customTitle') or 'agent') + '\t' + (rb.get('cwd') or '.') + '\t' + rb['command'])
PY
}
case "${1:-list}" in
  list) mapfile_cmds | cut -f1 | nl ;;
  all)
    win=""; [ -z "${CMUX_WORKSPACE_ID:-}" ] && win="--window $("$CMUX" current-window 2>/dev/null | head -1)"
    mapfile_cmds | while IFS=$'\t' read -r name cwd cmd; do
      "$CMUX" new-workspace --name "$name" --cwd "$cwd" --command "$cmd" $win --focus true >/dev/null 2>&1 \
        || echo "failed: $name" >&2
    done ;;
  *) mapfile_cmds | sed -n "${1}p" | cut -f3 | bash ;;
esac

To make it launchable from Spotlight, wrap it in a tiny .app bundle (Spotlight indexes .apps, not bare .sh files):

APP="$HOME/Applications/Resume cmux agents.app"
mkdir -p "$APP/Contents/MacOS"
cat > "$APP/Contents/Info.plist" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>CFBundleName</key><string>Resume cmux agents</string>
  <key>CFBundleIdentifier</key><string>local.resume-cmux-agents</string>
  <key>CFBundleExecutable</key><string>resume</string>
  <key>CFBundlePackageType</key><string>APPL</string>
  <key>CFBundleVersion</key><string>1.0</string>
  <key>LSUIElement</key><true/>
</dict></plist>
PLIST
cat > "$APP/Contents/MacOS/resume" <<'SH'
#!/bin/bash
exec /bin/bash "$HOME/.config/cmux/resume-cmux-agents.sh" all
SH
chmod +x "$APP/Contents/MacOS/resume"
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f "$APP"
mdimport "$APP"

Locally-built bundles carry no quarantine, so they launch without Gatekeeper friction. Type “Resume cmux agents” in Spotlight to fire it.

If the in-app updater refuses to run

If you update cmux via its built-in (Sparkle) updater and hit SUSparkleErrorDomain 4005 “remote port connection was invalidated” with an underlying “Failed to create installation cache directory”, the usual cause is a stale com.apple.quarantine attribute on the app bundle (it was downloaded with a browser). The cache dir and code signing are red herrings. Fix:

xattr -dr com.apple.quarantine /Applications/cmux.app
rm -rf "$HOME/Library/Caches/com.cmuxterm.app/org.sparkle-project.Sparkle/PersistentDownloads/"* \
       "$HOME/Library/Caches/com.cmuxterm.app/org.sparkle-project.Sparkle/Installation/"*

Then Check for Updates again.

P.S.

remote-control is your friend!

Appendix: how I arrived at the reboot setup

This is the investigation trail behind the Surviving a reboot section – kept separate because it is the “why”, not the “do this”.

Symptom. With autoResumeAgentSessions: true, agents came back fine after a normal quit-and-reopen, but after a macOS reboot the panes were fresh – new sessions, lost conversations.

Two distinct problems.

  1. cmux was not relaunching at all. The setting only runs when cmux reopens; it is not a boot daemon. Adding cmux as a Login Item fixed the relaunch. A trap while diagnosing this: don’t compare a process’s start time to kern.boottime – the machine can sit at the login screen for hours, and Login Items fire at login, not at kernel boot. Compare to the login time instead:

    for p in loginwindow Finder Dock; do pid=$(pgrep -x "$p"|head -1); \
      [ -n "$pid" ] && echo "$p: $(ps -o lstart= -p $pid)"; done
    ps -o lstart= -p "$(pgrep -x cmux | head -1)"
  2. Even once it relaunched, the cold-start restore came back fresh. cmux’s own session restore is gated by a per-pane wasAgentRunning flag. An interactive agent sitting idle at its prompt is recorded as wasAgentRunning=false, and the restore then skips auto-resume and starts it fresh. (Upstream: manaflow-ai/cmux#4269 added that gate to avoid resuming agents you had explicitly exited; it also catches idle-but-alive agents. I reported the reboot impact in #5802.) The fingerprint of a “came back fresh” boot – every pane wasAgentRunning=false, no process on --resume:

    ps -axo command | grep -E '/(claude|codex)' | grep -v grep \
      | grep -oE -- '--resume [0-9a-f-]+|--session-id [0-9a-f-]+' | sort | uniq -c
    
    python3 - <<'PY'
    import json, os
    d=json.load(open(os.path.expanduser('~/Library/Application Support/cmux/session-com.cmuxterm.app.json')))
    tot=run=0
    for w in d.get('windows',[]):
      for ws in w.get('tabManager',{}).get('workspaces',[]):
        for pn in ws.get('panels',[]):
          t=pn.get('terminal') or {}
          if t.get('resumeBinding'):
            tot+=1; run+= 1 if t.get('wasAgentRunning') else 0
    print(f"agent panes={tot} wasAgentRunning_true={run}")
    PY

    On cmux 0.64.10 I saw this on both a graceful reboot (0 of 12 resumed) and a forced reboot (0 of 15) – wasAgentRunning was 0 every time.

What changed. I updated cmux 0.64.10 -> 0.64.15 and kept the Login Item (and, as it turns out, “Reopen windows when logging back in” was off). After the next reboot, all 15 panes resumed:

python3 - <<'PY'
import json, os, subprocess, re
d=json.load(open(os.path.expanduser('~/Library/Application Support/cmux/session-com.cmuxterm.app.json')))
saved=set()
for w in d.get('windows',[]):
  for ws in w.get('tabManager',{}).get('workspaces',[]):
    for pn in ws.get('panels',[]):
      rb=(pn.get('terminal') or {}).get('resumeBinding')
      if rb:
        m=re.search(r'(?:--resume|resume)\s+([0-9a-f-]{36})', rb.get('command') or '')
        if m: saved.add(m.group(1))
out=subprocess.run("ps -axo command", shell=True, capture_output=True, text=True).stdout
running=set(re.findall(r'--session-id ([0-9a-f-]{36})', out)) | set(re.findall(r'(?:--resume|resume) ([0-9a-f-]{36})', out))
print(f"resumed {len(saved & running)} / {len(saved)}")
PY

reported resumed 15 / 15.

The open question. In that same snapshot wasAgentRunning was still 0 for every pane – so the resume did not come through the gate I expected; something else brought them back. I first guessed macOS “Reopen windows when logging back in” – but the next time I hit the restart dialog that box was unchecked, and I had never touched it (macOS remembers the last state), so it was almost certainly off during the successful reboot too. That effectively rules it out as the mechanism. Best current explanation: cmux 0.64.15’s own session restore, plus the Login Item relaunch, bring the agents back. A confirming reboot (box still off) is queued to nail down exactly which piece is load-bearing. Upstream thread: #5802.

Upstream: https://github.com/manaflow-ai/cmux/issues/5802

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.