# How cmux Implements Workspace Persistence Across App Restarts

> Discover how cmux ensures workspace persistence across app restarts. Learn how cmux serializes UI state to a JSON file and restores it on launch.

- Repository: [manaflow-ai/cmux](https://github.com/manaflow-ai/cmux)
- Tags: internals
- Published: 2026-03-29

---

**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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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`](https://github.com/manaflow-ai/cmux/blob/main/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

```swift
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`](https://github.com/manaflow-ai/cmux/blob/main/AppDelegate.swift) lines 57-66.

### Inspect the Persisted JSON File

```bash

# Display the workspace configuration

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

```

### Disable Automatic Restore for Testing

```bash
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`](https://github.com/manaflow-ai/cmux/blob/main/SessionPersistence.swift)).

### Test Persistence End-to-End

```bash

# 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`](https://github.com/manaflow-ai/cmux/blob/main/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.