cmux Debugging Tools: Complete Guide to Development and Testing

cmux ships with a full-stack debugging ecosystem that uses tagged Unix domain sockets, a high-performance ring-buffer logger, and JSON-RPC simulation commands to enable automated testing and deep inspection of the terminal multiplexer.

The manaflow-ai/cmux repository provides a comprehensive debugging infrastructure designed specifically for terminal emulator development. These tools allow developers to trace UI events, simulate keyboard input programmatically, and inspect internal state without manual interaction, all centered around a tagged debug socket architecture.

The Debug Socket Architecture

At the heart of cmux debugging is a Unix domain socket created at /tmp/cmux-debug-<tag>.sock that accepts JSON-RPC commands. This socket serves as the command bridge between the CLI, UI tests, and the running application instance.

Socket Creation and Auto-Discovery

The debug socket is created automatically when launching a debug build. In Sources/cmuxApp.swift at line 651, the application initializes the socket listener, while the corresponding client logic in CLI/cmux.swift at line 12935 handles auto-discovery. The CLI resolves the active socket through three methods:

  • The CMUX_SOCKET environment variable
  • The CMUX_TAG environment variable
  • Scanning /tmp/cmux-debug-*.sock for the most recent socket

This design allows multiple tagged instances to run side-by-side without conflicts.

Tagged Build Isolation

Use the provided build script to generate isolated debug instances:

./scripts/reload.sh --tag my-feature --launch

The scripts/reload.sh script builds a Debug app with a unique bundle ID and derived data path, then prints the socket location. Each tagged build writes to its own socket path and log file, enabling parallel development on multiple features.

Logging with dlog and DebugEventLog

cmux implements a lightweight, thread-safe logging system through the dlog helper macro, which feeds into a 500-entry ring buffer managed by DebugEventLog.

The Ring Buffer Implementation

Every dlog() call expands to DebugEventLog.shared.log(message), storing timestamped events in memory. The implementation resides in the Bonsplit submodule at vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift (referenced in CLAUDE.md at line 126). This ring buffer captures the last 500 events regardless of log levels, ensuring critical context is available during crashes.

Writing and Dumping Debug Logs

Insert debug statements anywhere in the Swift codebase:

// In Sources/Workspace.swift or any DEBUG-guarded section
dlog("🪲 Workspace layout refreshed after split")

Messages appear in both the in-memory buffer and /tmp/cmux-debug-<tag>.log. To persist the full buffer to disk programmatically:


# From within a running process (debugger or temporary UI button)

DebugEventLog.shared.dump()

This writes ~/Library/Application Support/cmux/debug-log-<timestamp>.txt containing the complete event history since launch.

Simulation Commands and JSON-RPC Interface

The debug socket exposes a JSON-RPC interface for driving the UI without human interaction, enabling automated integration testing and precise reproduction of bug scenarios.

Available Simulation Commands

The Sources/TerminalController.swift file implements command parsing (see the help text near line 11146). Key commands include:

  • simulate_type – Injects text into the focused terminal pane
  • simulate_shortcut – Triggers keyboard shortcuts (e.g., cmd+w)
  • layout_debug – Dumps current terminal layout state

These commands mirror user interactions exactly, making them ideal for regression testing complex window management behaviors.

Sending Commands via Netcat

Interact with a running instance directly through the socket:


# Type "hello world" into the active terminal

printf '{"method":"simulate_type","params":["hello world"]}\n' | \
  nc -U /tmp/cmux-debug-my-feature.sock

For CLI convenience, the cmux binary handles socket discovery automatically:


# Auto-discovers socket and sends shortcut

cmux simulate_shortcut cmd+w

# Or specify explicitly

cmux --socket /tmp/cmux-debug-my-feature.sock simulate_shortcut cmd+w

Debug-Only UI Helpers

The codebase includes a family of debug* methods exposed only in Debug builds, guarded by #if DEBUG preprocessor directives. These methods, such as debugRenderStats() and debugHasSearchOverlay(), expose internal view state for validation in unit tests.

In cmuxTests/TerminalAndGhosttyTests.swift at line 1899, you can see examples of these helpers validating rendering pipelines and overlay states. They provide programmatic access to visual properties that are otherwise opaque to the testing framework.

Summary

  • Tagged debug sockets at /tmp/cmux-debug-<tag>.sock provide isolated JSON-RPC command channels for each build instance, created in Sources/cmuxApp.swift and discovered via CLI/cmux.swift.
  • The dlog system writes to a 500-entry ring buffer in DebugEventLog, with automatic log files at /tmp/cmux-debug-<tag>.log.
  • Simulation commands like simulate_type and simulate_shortcut enable scriptable UI automation through Sources/TerminalController.swift.
  • scripts/reload.sh --tag <name> generates isolated debug builds that won't conflict with production instances.
  • Debug-only view helpers expose internal state for unit testing when compiled with DEBUG flags.

Frequently Asked Questions

How do I connect to a running cmux debug instance?

The cmux CLI automatically discovers the correct socket by checking CMUX_SOCKET, CMUX_TAG, or scanning /tmp/cmux-debug-*.sock as implemented in CLI/cmux.swift at line 12935. Simply run cmux status to verify which socket path the CLI will use, then issue commands like cmux simulate_type "text".

What is the difference between dlog and standard logging?

dlog is a specialized, high-performance logger that writes to a fixed 500-entry ring buffer in DebugEventLog, making it ideal for capturing recent history during crashes without flooding the console. Unlike standard print statements, dlog entries are both memory-buffered and written to /tmp/cmux-debug-<tag>.log, and can be dumped to persistent storage via DebugEventLog.shared.dump().

How do I run multiple debug builds simultaneously?

Use the ./scripts/reload.sh --tag <unique-name> command for each instance. This script generates builds with unique bundle IDs and derived data paths, ensuring each instance creates its own socket at /tmp/cmux-debug-<tag>.sock and log file. You can then target specific instances using CMUX_TAG=<tag> or the --socket CLI flag.

Where are debug logs stored?

Debug logs exist in two locations: a transient human-readable file at /tmp/cmux-debug-<tag>.log that updates in real-time, and a persistent dump at ~/Library/Application Support/cmux/debug-log-<timestamp>.txt created when calling DebugEventLog.shared.dump(). You can monitor live activity with tail -f /tmp/cmux-debug-<tag>.log during development sessions.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →