Best Practices for Using the cmux Socket API to Control Workspaces
The cmux socket API exposes a Unix-domain socket interface that allows external scripts and automation tools to create, query, and manipulate terminal workspaces programmatically while enforcing focus policies to prevent UI disruption.
The manaflow-ai/cmux repository provides a terminal multiplexer with a robust socket-based control system. The TerminalController class services API requests over a Unix-domain socket, validating commands and forwarding workspace operations to the main-thread TabManager. This architecture enables CI pipelines and automation agents to manage workspaces without user interaction, provided they respect the focus-policy guards that prevent accidental focus stealing.
Understanding the cmux Socket API Architecture
The socket API is implemented in Sources/TerminalController.swift and operates as a single-threaded, main-actor service that receives plain-text or JSON commands. External clients connect to a Unix-domain socket—defaulting to /tmp/cmux.sock or a tag-specific path—and send line-delimited commands that the controller parses and executes.
Core Command Flow
When the application starts via TerminalController.start, it initializes a SocketListenerHealth monitoring system and begins an accept loop on the Unix socket. Each incoming line is processed in handleCommand(_:), which matches the command key against a dispatch switch. Workspace-related commands follow this routing:
new_workspace→newWorkspace(_:)(line 1675)list_workspaces→listWorkspaces()(line 1672)select_workspace→selectWorkspace(_:)(line 1691)current_workspace→currentWorkspace()(line 1694)close_workspace→closeWorkspace(_:)(line 1678)
Thread Safety and Main-Thread Execution
Because UI state lives exclusively on the main thread, every workspace operation wraps its execution in DispatchQueue.main.sync. For example, newWorkspace implements this protection at lines 12185-12189, ensuring thread-safe mutations while keeping the socket listener responsive. This design guarantees that socket commands never create race conditions with the graphical interface.
Focus-Policy Guards
The API enforces strict focus-management policies through socketCommandAllowsInAppFocusMutations() and a static whitelist (focusIntentV1Commands and focusIntentV2Methods) defined at lines 18-27. Workspace commands generally do not change UI focus unless explicitly permitted. The select_workspace command respects the socket-policy flag, meaning the UI will only switch workspaces if the socket operates in a focus-allowed context (e.g., -socketControlMode allowAll). This prevents background automation scripts from disrupting an active user session.
Essential Workspace Commands
The v1 plain-text protocol uses simple line-based commands that return OK <data> or error messages. For structured automation, the v2 JSON API provides versioned methods with typed parameters.
| Command | Purpose | Response Format |
|---|---|---|
new_workspace <title> |
Creates a workspace | OK <uuid> |
list_workspaces |
Returns all workspaces | * 0: <uuid> <title> per line |
select_workspace <uuid|index> |
Activates a workspace | OK or error |
current_workspace |
Queries active workspace | OK <uuid> or error |
close_workspace <uuid> |
Removes a workspace | OK or protection error |
The listWorkspaces implementation (lines 12171-12175) prefixes the current workspace with * and outputs zero-based indices, while selectWorkspace (lines 13184-13186) validates indices against the current tab count before switching.
Recommended Usage Patterns
Creating and Selecting Workspaces
To create a workspace and immediately switch to it, chain the commands using the UUID returned from creation:
- Send
new_workspace MyProjectto receiveOK <uuid> - Send
select_workspace <uuid>to activate it
This sequence respects the focus-policy; the UI will only change if the socket context allows focus mutations. For guaranteed focus changes, launch cmux with -socketControlMode allowAll.
Listing and Querying Workspaces
Use list_workspaces for workspace discovery in automation scripts. The output format provides both UUIDs and indices:
* 0: abc-123 MainProject
1: def-456 Secondary
Parse the * prefix to identify the currently active workspace without issuing a separate current_workspace call.
Closing Workspaces Safely
Before removing workspaces, check for protection flags using close_workspace. The command invokes workspaceCloseProtectedMessage() to verify the workspace can be closed. Always use UUIDs rather than indices when closing workspaces to avoid accidental deletion if the tab order changes between commands.
Index-Based vs UUID-Based Selection
While select_workspace accepts both UUIDs and zero-based indices, prefer UUIDs for deterministic identification. Indices are brittle when workspaces are reordered, whereas UUIDs remain stable for the workspace lifetime. Use index-based selection (e.g., select_workspace 2) only when the UUID is unknown and the tab order is static.
Implementation Examples
Bash One-Liners with netcat
For quick shell automation, use nc to send commands to the Unix socket:
# Use the environment variable or default path
SOCK=${CMUX_SOCKET:-/tmp/cmux.sock}
# Create workspace and capture UUID
WS_ID=$(echo "new_workspace demo" | nc -U "$SOCK" | awk '{print $2}')
echo "Created workspace $WS_ID"
# Activate the workspace
echo "select_workspace $WS_ID" | nc -U "$SOCK"
The nc -U command writes the line, reads the newline-terminated response, and awk extracts the UUID from the OK prefix.
Swift ControlSocketClient
For native integration, use the ControlSocketClient class from Sources/ControlSocketClient.swift:
let client = ControlSocketClient(path: "/tmp/cmux.sock", responseTimeout: 2.0)
// Create workspace
if let reply = client.sendLine("new_workspace automation") {
let wsId = reply.split(separator: " ").last!
// Select it
_ = client.sendLine("select_workspace \(wsId)")
}
Reference the UI tests in cmuxUITests/SidebarHelpMenuUITests.swift (line 971) for additional client usage patterns.
Python Scripts for CI Automation
For robust CI pipelines, implement explicit socket handling with timeouts:
import socket, os, json, sys
sock_path = os.getenv("CMUX_SOCKET", "/tmp/cmux.sock")
def send(cmd):
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.settimeout(2.0)
s.connect(sock_path)
s.sendall((cmd + "\n").encode())
return s.recv(4096).decode().strip()
# Create and select workspace
response = send("new_workspace ci-run")
if response.startswith("OK"):
ws = response.split()[1]
print("Created:", ws)
print(send(f"select_workspace {ws}"))
else:
sys.exit(1)
This implementation validates the OK prefix and raises exceptions on failure, allowing CI systems to capture errors appropriately.
JSON v2 API for Structured Automation
For new automation code, prefer the v2 JSON API implemented in TerminalController.handleJSON(_:) around line 1010. It provides structured responses and better error handling:
REQ='{"method":"workspace.select","params":{"workspace_id":"'"$WS_ID"'"}}'
echo "$REQ" | nc -U "$CMUX_SOCKET"
The v2 API returns objects like {"result":"OK"} or detailed error codes, offering future-proof compatibility as the API evolves. See docs/v2-api-migration.md for migration guidance from v1 plain-text commands.
Configuration and Security Best Practices
Socket Path Management
Always set the CMUX_SOCKET environment variable or pass --socket <path> to the cmux CLI to ensure your script communicates with the correct, possibly-tagged socket instance. The default path configuration resides in Sources/SocketControlSettings.swift. When connecting immediately after launch, implement a polling backoff:
while ! nc -zU "$SOCK" 2>/dev/null; do sleep 0.2; done
Focus Policy and Socket Control Modes
Respect the focus-intent whitelist defined in docs/socket-focus-steal-audit.todo.md. By default, workspace commands do not change UI focus. Use -socketControlMode allowAll only when the automation explicitly requires focus changes, such as during dedicated automation runs without active users.
Response Validation and Error Handling
Validate that responses start with OK (v1) or contain "result":"OK" (v2). Treat any other prefix as a failure. The socket may return errors for protected workspaces or invalid indices; check workspaceCloseProtectedMessage() logic in the source to understand protection scenarios.
Summary
- Use UUIDs for stability: Prefer UUID-based selection over indices to avoid errors when workspace order changes.
- Respect focus policies: Only use
-socketControlMode allowAllwhen UI focus changes are explicitly required; default modes prevent disruption. - Validate responses: Check for
OKprefixes or JSON result fields to confirm command success. - Prefer JSON v2: Adopt the v2 JSON API for new automation to ensure typed parameters and future compatibility.
- Handle socket readiness: Poll for socket existence with backoff immediately after launching cmux.
- Leverage built-in clients: Use
ControlSocketClientin Swift or manual socket handling in Python/Bash with proper timeouts.
Frequently Asked Questions
What socket path does cmux use by default?
By default, cmux creates its control socket at /tmp/cmux.sock. However, when using tagged instances or custom configurations, the path may vary. Always check the CMUX_SOCKET environment variable or the settings defined in Sources/SocketControlSettings.swift to locate the active socket for your session.
How do I prevent my automation script from stealing focus?
The cmux socket API implements focus-policy guards through socketCommandAllowsInAppFocusMutations() and a whitelist of focus-intent commands. By default, workspace operations like new_workspace do not change UI focus. Only select_workspace can change focus, and only when the socket operates in allowAll mode. Run cmux without focus-permissive flags to keep automation non-intrusive.
Should I use UUIDs or indices when selecting workspaces?
Prefer UUIDs for all deterministic automation. While select_workspace accepts zero-based indices (validated at lines 13184-13186 in TerminalController.swift), indices change when workspaces are reordered. UUIDs returned by new_workspace and list_workspaces remain stable for the workspace lifetime, preventing race conditions in concurrent automation.
What is the difference between v1 and v2 socket APIs?
The v1 API uses plain-text commands like new_workspace and returns simple OK or error strings. The v2 API, accessible via JSON requests to the same socket, offers structured methods like workspace.select, typed parameters, and JSON error objects. According to docs/v2-api-migration.md, the v2 API provides better error handling, versioned methods, and backward compatibility guarantees, making it the recommended choice for new automation implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →