How cmux Implements Workspace Persistence Across App Restarts

cmux serializes the complete UI state—including windows, workspaces, panes, and scrollback—to a versioned JSON file in ~/Library/Application Support/cmux/ and restores it on launch using a hierarchy of Codable structs.

cmux is an open-source terminal and workspace manager that must maintain complex UI state across system restarts. The application implements workspace persistence by capturing a deterministic snapshot of the entire session tree and persisting it atomically to disk, then reconstructing the interface from that snapshot during startup.

The Snapshot Data Model

At the core of cmux's persistence layer is a tree of Codable structs defined in Sources/SessionPersistence.swift. These structures mirror the observable UI hierarchy exactly:

  • AppSessionSnapshot – Root container holding all windows and global settings
  • SessionWindowSnapshot – Captures window frames and sidebar width
  • SessionWorkspaceSnapshot – Stores workspace titles, colors, pins, and selected state
  • Pane and panel snapshots – Terminal content, scrollback buffers, and split configurations

This hierarchy lives in SessionPersistence.swift lines 31-73, where each struct conforms to Codable to enable deterministic serialization. The model captures every detail necessary to recreate the user's environment, including scrollback position and active directory paths.

Where Session Data Is Stored

By default, cmux writes the snapshot to a JSON file located at:


~/Library/Application Support/cmux/session-<bundle-id>.json

The bundle identifier defaults to com.cmuxterm.app. You can retrieve the exact path programmatically via SessionPersistenceStore.defaultSnapshotFileURL(), which constructs the standardized Application Support path (lines 402-425 in SessionPersistence.swift). The file is written atomically to prevent corruption during system shutdown or crashes.

When and How cmux Saves State

The application triggers saves through three distinct mechanisms to balance data integrity with performance:

1. Full Persistence on Quit When the user quits or the system powers off, AppDelegate.applicationWillTerminate calls saveSessionSnapshot(includeScrollback: true, removeWhenEmpty: false). This captures the complete state including full terminal scrollback (lines 59-66 in Sources/AppDelegate.swift).

2. Lightweight Save on Deactivation When the app resigns active status (switching to another app), applicationWillResignActive creates a lightweight snapshot without scrollback (includeScrollback: false) to ensure rapid context switches are preserved (lines 71-73 in AppDelegate.swift).

3. Periodic Autosave A timer managed by startSessionAutosaveTimerIfNeeded fires every 8 seconds (SessionPersistencePolicy.autosaveInterval). It calls saveSessionSnapshot(includeScrollback: false) only if the UI fingerprint has changed, as determined by shouldSkipSessionAutosaveForUnchangedFingerprint (lines 17-33 in AppDelegate.swift).

The actual disk write occurs in SessionPersistenceStore.save(_:fileURL:), which uses JSONEncoder to serialize the snapshot and writes it atomically to the target path (lines 74-88 in SessionPersistence.swift).

Restoring Workspaces on Launch

During startup, AppDelegate.prepareStartupSessionSnapshotIfNeeded checks SessionRestorePolicy.shouldAttemptRestore() to determine if restoration should proceed. This policy aborts if the user disabled restore via environment variable or if the app is running in test mode (lines 78-84 in AppDelegate.swift).

If restoration proceeds, SessionPersistenceStore.load() deserializes the JSON file into an AppSessionSnapshot. The UI layer then reconstructs windows, workspaces, and tabs using this data structure. The system verifies the snapshot's version field (currently SessionSnapshotSchema.currentVersion = 1) before applying it, preventing crashes when the schema evolves (lines 65-70 in SessionPersistence.swift).

Handling Terminal Scrollback

Full terminal scrollback is only persisted during the quit-phase save to keep autosave files lightweight. For regular autosaves, SessionPersistencePolicy.truncatedScrollback strips this heavy data.

When restoring a snapshot that contains scrollback, SessionScrollbackReplayStore handles the complexity. It writes the scrollback content to a temporary file and injects an environment variable (CMUX_SCROLLBACK_REPLAY_PATH) so the terminal process can replay the text on launch (lines 34-45 in SessionScrollbackReplayStore). This separates the metadata (JSON) from the bulky text content (temp files).

Version Safety and Schema Migration

Each snapshot carries a version integer. During load, SessionPersistenceStore validates this version against SessionSnapshotSchema.currentVersion. If a mismatch occurs, the system aborts restoration to prevent undefined behavior from schema changes. This versioning strategy ensures forward compatibility as cmux adds new features to the workspace model.

Code Examples

Trigger a Manual Full Session Save

AppDelegate.shared?.saveSessionSnapshot(includeScrollback: true, removeWhenEmpty: false)

This persists the complete snapshot including scrollback buffers, identical to the quit-phase behavior defined in AppDelegate.swift lines 57-66.

Inspect the Persisted JSON File


# Display the workspace configuration

cat ~/Library/Application\ Support/cmux/session-com.cmuxterm.app.json | jq '.workspaces[0]'

Disable Automatic Restore for Testing

export CMUX_DISABLE_SESSION_RESTORE=1
open -a cmux

SessionRestorePolicy.shouldAttemptRestore() checks this environment variable and aborts loading if set (lines 19-27 in SessionPersistence.swift).

Test Persistence End-to-End


# Create a workspace with content

cmux new-workspace
cmux open https://github.com/manaflow-ai/cmux

# Quit the app (Cmd-Q) - triggers full save

# Relaunch and verify restoration

open -a cmux

Summary

  • Data Model: Hierarchical Codable structs in SessionPersistence.swift mirror the entire UI state
  • Storage Location: ~/Library/Application Support/cmux/session-<bundle-id>.json
  • Save Triggers: App quit (full), resign active (lightweight), and 8-second autosave (delta)
  • Scrollback Strategy: Full content only on quit; lightweight autosaves exclude terminal history
  • Safety: Version checking prevents crashes from outdated snapshot schemas
  • Restoration: JSON decoding during prepareStartupSessionSnapshotIfNeeded rebuilds the interface

Frequently Asked Questions

How do I clear the saved workspace state?

Delete the JSON file at ~/Library/Application Support/cmux/session-com.cmuxterm.app.json (or your specific bundle ID). Alternatively, quit all windows and workspaces, then quit cmus—the removeWhenEmpty: true parameter (when applicable) cleans up the file automatically.

Why doesn't my scrollback restore after force-quitting the app?

Scrollback is only persisted during graceful shutdown via applicationWillTerminate. If the app crashes or is force-quit, only the lightweight autosave (without scrollback) remains. This design prevents massive JSON files from being written every 8 seconds during normal operation.

Can I move the persistence file to a custom location?

The path is hardcoded to the Application Support directory via SessionPersistenceStore.defaultSnapshotFileURL() in lines 402-425. While you cannot officially relocate it through preferences, you can symlink ~/Library/Application Support/cmux to another volume if you need to store sessions on external storage.

Does cmux support migration between versions?

Yes. The snapshot format includes a version field (currently version 1). When loading, SessionPersistenceStore validates this version and aborts restoration if the schema has changed, preventing data corruption. Future updates may implement migration logic when the schema version increments.

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 →