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_SOCKETenvironment variable - The
CMUX_TAGenvironment variable - Scanning
/tmp/cmux-debug-*.sockfor 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 panesimulate_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>.sockprovide isolated JSON-RPC command channels for each build instance, created inSources/cmuxApp.swiftand discovered viaCLI/cmux.swift. - The
dlogsystem writes to a 500-entry ring buffer inDebugEventLog, with automatic log files at/tmp/cmux-debug-<tag>.log. - Simulation commands like
simulate_typeandsimulate_shortcutenable scriptable UI automation throughSources/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
DEBUGflags.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →