Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7f4d8c3
Restore previous sessions and resume agents
austinywang Apr 15, 2026
09d1941
Add relaunch regression coverage for session restore
austinywang Apr 18, 2026
8fc8cce
Persist exact agent launch commands for resume
lawrencecchen Apr 22, 2026
269e063
Keep restored agent resume pending for lazy terminals
lawrencecchen Apr 22, 2026
3ce9dda
Add OpenCode resume hook plugin
lawrencecchen Apr 22, 2026
96e09a9
merge: resolve conflicts with main
lawrencecchen Apr 22, 2026
6715479
fix: address session restore review feedback
lawrencecchen Apr 22, 2026
babaaa7
fix: keep socket session restore from activating new windows
lawrencecchen Apr 22, 2026
99237d0
fix: strip stale node options from agent restore
lawrencecchen Apr 22, 2026
1ef4eb8
fix: make opencode plugin valid esm
lawrencecchen Apr 22, 2026
45e2afd
fix: include agent resume metadata in autosave fingerprint
lawrencecchen Apr 22, 2026
dbaf8a4
fix: avoid saving incomplete manual restores
lawrencecchen Apr 22, 2026
fa46ce1
fix: use v2 main sync for session restore
lawrencecchen Apr 22, 2026
6162612
fix: harden session reopen edge cases
lawrencecchen Apr 22, 2026
cf691f6
fix: clarify claude resume reject options
lawrencecchen Apr 22, 2026
91723d0
fix: keep agent fingerprint test pure
lawrencecchen Apr 22, 2026
fc5aac5
fix: register opencode restore plugin
lawrencecchen Apr 22, 2026
97ac6aa
fix: avoid opencode plugin package resolution
lawrencecchen Apr 22, 2026
d41d18d
fix: resume claude and opencode restores
lawrencecchen Apr 22, 2026
e0d3462
fix: keep agent resume commands under input limits
lawrencecchen Apr 22, 2026
c4b475b
fix: invalidate stale restored agent snapshots
lawrencecchen Apr 22, 2026
230824c
Merge remote-tracking branch 'origin/main' into issue-2923-reopen-ses…
lawrencecchen Apr 22, 2026
0c2c257
fix: tighten agent resume hook persistence
lawrencecchen Apr 22, 2026
b9efe4c
fix: avoid empty claude launch argv entries
lawrencecchen Apr 22, 2026
9cd3d82
fix: bound restored agent startup input
lawrencecchen Apr 22, 2026
450b16c
fix: prune stale restored agent state
lawrencecchen Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,247 changes: 1,234 additions & 13 deletions CLI/cmux.swift

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
A5001621 /* AppleScriptSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001620 /* AppleScriptSupport.swift */; };
A5001623 /* cmux.sdef in Resources */ = {isa = PBXBuildFile; fileRef = A5001622 /* cmux.sdef */; };
A5001640 /* RemoteRelayZshBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001641 /* RemoteRelayZshBootstrap.swift */; };
A5001660 /* RestorableAgentSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001661 /* RestorableAgentSession.swift */; };
A5001650 /* CmuxConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001651 /* CmuxConfig.swift */; };
A5001652 /* CmuxConfigExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001653 /* CmuxConfigExecutor.swift */; };
A5001654 /* CmuxDirectoryTrust.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001655 /* CmuxDirectoryTrust.swift */; };
Expand Down Expand Up @@ -298,6 +299,7 @@
A5001620 /* AppleScriptSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptSupport.swift; sourceTree = "<group>"; };
A5001622 /* cmux.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.sdef; path = cmux.sdef; sourceTree = "<group>"; };
A5001641 /* RemoteRelayZshBootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRelayZshBootstrap.swift; sourceTree = "<group>"; };
A5001661 /* RestorableAgentSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableAgentSession.swift; sourceTree = "<group>"; };
A5001651 /* CmuxConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmuxConfig.swift; sourceTree = "<group>"; };
A5001653 /* CmuxConfigExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmuxConfigExecutor.swift; sourceTree = "<group>"; };
A5001655 /* CmuxDirectoryTrust.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CmuxDirectoryTrust.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -543,6 +545,7 @@
A5001241 /* WindowDecorationsController.swift */,
A5001222 /* WindowAccessor.swift */,
A5001611 /* SessionPersistence.swift */,
A5001661 /* RestorableAgentSession.swift */,
A5001641 /* RemoteRelayZshBootstrap.swift */,
A5001651 /* CmuxConfig.swift */,
A5001653 /* CmuxConfigExecutor.swift */,
Expand Down Expand Up @@ -904,6 +907,7 @@
A5001240 /* WindowDecorationsController.swift in Sources */,
A500120C /* WindowAccessor.swift in Sources */,
A5001610 /* SessionPersistence.swift in Sources */,
A5001660 /* RestorableAgentSession.swift in Sources */,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
A5001640 /* RemoteRelayZshBootstrap.swift in Sources */,
A5001650 /* CmuxConfig.swift in Sources */,
A5001652 /* CmuxConfigExecutor.swift in Sources */,
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Browser developer-tool shortcuts follow Safari defaults and are customizable in
| Shortcut | Action |
|----------|--------|
| ⌘ ⇧ N | New window |
| ⌘ ⇧ O | Reopen previous session |
| ⌘ , | Settings |
| ⌘ ⇧ , | Reload configuration |
| ⌘ Q | Quit |
Expand All @@ -236,15 +237,21 @@ cmux NIGHTLY is a separate app with its own bundle ID, so it runs alongside the

Report nightly bugs on [GitHub Issues](https://github.com/manaflow-ai/cmux/issues) or in [#nightly-bugs on Discord](https://discord.gg/xsgFEVrWCZ).

## Session restore (current behavior)
## Session restore

On relaunch, cmux currently restores app layout and metadata only:
Quitting cmux saves the current session. On relaunch, cmux restores:
- Window/workspace/pane layout
- Working directories
- Terminal scrollback (best effort)
- Browser URL and navigation history
- Saved Claude Code and Codex sessions, when cmux has a resume token for the panel

cmux does **not** restore live process state inside terminal apps. For example, active Claude Code/tmux/vim sessions are not resumed after restart yet.
If you need to reapply the last saved snapshot manually, use:
- `File > Reopen Previous Session`
- `⌘ ⇧ O`
- `cmux restore-session`

cmux does **not** restore arbitrary live terminal process state. tmux, vim, shells, and other tools without a cmux resume flow still reopen as normal terminals rather than resuming in-process state.

## Star History

Expand Down
87 changes: 86 additions & 1 deletion Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -22034,6 +22034,40 @@
}
}
},
"command.reopenPreviousSession.subtitle": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Session"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "セッション"
}
}
}
},
"command.reopenPreviousSession.title": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reopen Previous Session"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "前回のセッションを再度開く"
}
}
}
},
"command.restartSocketListener.subtitle": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -39888,6 +39922,23 @@
}
}
},
"menu.file.reopenPreviousSession": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reopen Previous Session"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "前回のセッションを再度開く"
}
}
}
},
"menu.find.find": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -70367,6 +70418,23 @@
}
}
},
"shortcut.reopenPreviousSession.label": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Reopen Previous Session"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "前回のセッションを再度開く"
}
}
}
},
"shortcut.selectSurfaceByNumber.label": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -91029,6 +91097,23 @@
}
}
},
"terminal.restore.no_snapshot": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "No previous session snapshot available"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "前回のセッションスナップショットがありません"
}
}
}
},
"settings.account.error.missingAccessToken": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -91059,4 +91144,4 @@
}
}
}
}
}
40 changes: 38 additions & 2 deletions Resources/bin/claude
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ ensure_node_options_restore_module() {
local guard_path="$guard_dir/restore-node-options.cjs"
mkdir -p "$guard_dir" || return 1
local temp_path
temp_path="$(mktemp "$guard_dir/restore-node-options.XXXXXX.cjs")" || return 1
temp_path="$(mktemp "$guard_dir/restore-node-options.cjs.XXXXXX")" || return 1
cat >"$temp_path" <<'EOF'
const hadOriginalNodeOptions = process.env.CMUX_ORIGINAL_NODE_OPTIONS_PRESENT === "1";
if (hadOriginalNodeOptions) {
Expand Down Expand Up @@ -162,6 +162,38 @@ merge_node_options() {
printf ' %s' "${filtered[@]}"
}

normalize_node_options_for_restore() {
local existing="${1:-}"
local -a tokens=()
local -a normalized=()
local token
local index=0

read -r -a tokens <<<"$existing"
while (( index < ${#tokens[@]} )); do
token="${tokens[$index]}"
if [[ "$token" == "--max-old-space-size" && $((index + 1)) -lt ${#tokens[@]} ]]; then
normalized+=("--max-old-space-size=${tokens[$((index + 1))]}")
index=$((index + 2))
continue
fi
normalized+=("$token")
index=$((index + 1))
done

if (( ${#normalized[@]} == 0 )); then
return 0
fi
printf '%s' "${normalized[0]}"
if (( ${#normalized[@]} > 1 )); then
printf ' %s' "${normalized[@]:1}"
fi
}

encode_launch_argv() {
{ printf '%s\0' "$REAL_CLAUDE"; printf '%s\0' "$@"; } | base64 | tr -d '\n'
}
Comment thread
cursor[bot] marked this conversation as resolved.

# Pass through subcommands that don't support session/hook flags.
case "${1:-}" in
mcp|config|api-key|rc|remote-control) exec "$REAL_CLAUDE" "$@" ;;
Expand All @@ -187,10 +219,14 @@ done
# the actual claude process PID, which hooks use for stale-session detection.
export CMUX_CLAUDE_PID=$$
export CMUX_CLAUDE_HOOK_CMUX_BIN="$(resolve_hook_cmux_bin)"
export CMUX_AGENT_LAUNCH_KIND="claude"
export CMUX_AGENT_LAUNCH_EXECUTABLE="$REAL_CLAUDE"
export CMUX_AGENT_LAUNCH_ARGV_B64="$(encode_launch_argv "$@")"
export CMUX_AGENT_LAUNCH_CWD="$PWD"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if GUARD_PATH="$(ensure_node_options_restore_module)"; then
if [[ ${NODE_OPTIONS+x} ]]; then
export CMUX_ORIGINAL_NODE_OPTIONS_PRESENT=1
export CMUX_ORIGINAL_NODE_OPTIONS="$NODE_OPTIONS"
export CMUX_ORIGINAL_NODE_OPTIONS="$(normalize_node_options_for_restore "$NODE_OPTIONS")"
else
export CMUX_ORIGINAL_NODE_OPTIONS_PRESENT=0
unset CMUX_ORIGINAL_NODE_OPTIONS
Expand Down
Loading
Loading