Repository navigation
Conversation
…windows After session restore, new shell processes would overwrite restored workspace titles with generic names (e.g. "zsh"), making all workspaces look identical. Add a `restoredSessionProcessTitle` guard that suppresses the first shell title update, preserving the meaningful restored title until a genuinely different process title arrives. Also set `isRestorable = false` on all cmux windows to prevent macOS native state restoration from creating duplicate windows on relaunch — cmux manages its own session persistence via SessionPersistenceStore. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@Apptah is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR prevents macOS native window state restoration from interfering with the application's own session persistence by disabling window restorability at initialization and adding state tracking for process titles during session restoration, preventing duplicate windows and title conflicts on relaunches. Changes
Sequence DiagramsequenceDiagram
participant User
participant AppInit as App Initialization
participant MacOS as macOS System
participant Workspace as Workspace State
participant Shell as Shell/Process
User->>AppInit: Launch app with saved session
AppInit->>AppInit: Set window.isRestorable = false
AppInit->>MacOS: Disable native window restoration
AppInit->>Workspace: restoreSessionSnapshot()
Workspace->>Workspace: Capture processTitle to restoredSessionProcessTitle
Note over Workspace: Guard active: only restore title<br/>until first genuine update
Workspace->>Shell: Restore shell context
Shell->>Workspace: updatePanelTitle() with generic title
Workspace->>Workspace: Match check: generic title vs restored?
alt Title matches restored
Workspace->>Workspace: Skip update, keep guard active
else First genuine title differs
Workspace->>Workspace: Clear guard (restoredSessionProcessTitle = nil)
Workspace->>Workspace: Apply new title update
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
1 issue found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Workspace.swift">
<violation number="1" location="Sources/Workspace.swift:6044">
P2: Restore-title guard only blocks exact matches; first differing startup titles still overwrite restored workspace titles.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| if let restoredTitle = restoredSessionProcessTitle { | ||
| // The new shell's initial title matches or is less informative | ||
| // than the restored title — keep the restored one. | ||
| if trimmed != restoredTitle { |
There was a problem hiding this comment.
P2: Restore-title guard only blocks exact matches; first differing startup titles still overwrite restored workspace titles.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Workspace.swift, line 6044:
<comment>Restore-title guard only blocks exact matches; first differing startup titles still overwrite restored workspace titles.</comment>
<file context>
@@ -6018,14 +6032,33 @@ final class Workspace: Identifiable, ObservableObject {
+ if let restoredTitle = restoredSessionProcessTitle {
+ // The new shell's initial title matches or is less informative
+ // than the restored title — keep the restored one.
+ if trimmed != restoredTitle {
+ restoredSessionProcessTitle = nil
+ if self.title != trimmed {
</file context>
Greptile SummaryThis PR addresses two distinct session-restore bugs in cmux: duplicate windows on relaunch (via macOS native state restoration conflicting with cmux's own Key changes:
Confidence Score: 4/5The duplicate-window fix is safe to merge; the title-preservation fix has a logic gap that leaves command-name titles still overwritable by the initial shell title. The isRestorable = false changes are straightforwardly correct and resolve the duplicate-window regression. The restoredSessionProcessTitle guard introduces a P1 logic issue: it clears on any incoming title that differs from the restored one, meaning command-name workspace titles (e.g. 'vim') would still be overwritten by the shell's initial title ('zsh' or current directory). The fix only reliably covers directory-as-title workspaces. The multi-panel stale-guard concern is P2 but worth addressing. Sources/Workspace.swift — the restoredSessionProcessTitle guard logic and its initialization for multi-panel workspaces. Important Files Changed
Sequence DiagramsequenceDiagram
participant App
participant Workspace
participant Shell
participant macOS
App->>Workspace: applySessionSnapshot(snapshot)
Workspace->>Workspace: applyProcessTitle(snapshot.processTitle)
Workspace->>Workspace: setCustomTitle(snapshot.customTitle)
Note over Workspace: restoredSessionProcessTitle = nil (inside setCustomTitle)
Workspace->>Workspace: restoredSessionProcessTitle = snapshot.processTitle
Note over Workspace: Guard active (if customTitle==nil and processTitle non-empty)
Shell->>Workspace: updatePanelTitle(panelId, "zsh")
alt trimmed == restoredSessionProcessTitle
Note over Workspace: Suppress update — restored title preserved
else trimmed != restoredSessionProcessTitle
Workspace->>Workspace: restoredSessionProcessTitle = nil
Workspace->>Workspace: self.title = trimmed (guard cleared, title overwritten)
end
Shell->>Workspace: updatePanelTitle(panelId, "git log")
Note over Workspace: Guard nil — normal update, self.title = "git log"
App->>macOS: window.isRestorable = false
Note over macOS: macOS native restore disabled — no duplicate windows
Reviews (1): Last reviewed commit: "Fix session restore: preserve workspace ..." | Re-trigger Greptile |
| if let restoredTitle = restoredSessionProcessTitle { | ||
| // The new shell's initial title matches or is less informative | ||
| // than the restored title — keep the restored one. | ||
| if trimmed != restoredTitle { | ||
| restoredSessionProcessTitle = nil | ||
| if self.title != trimmed { | ||
| self.title = trimmed | ||
| didMutate = true | ||
| } | ||
| if processTitle != trimmed { | ||
| processTitle = trimmed | ||
| } | ||
| } |
There was a problem hiding this comment.
Guard only suppresses when titles match, not the initial shell title
The guard clears (and immediately allows the new title through) when trimmed != restoredTitle. This means the fix only preserves the restored title when the shell's very first reported title happens to exactly equal the restored title — the "directory as title" case. For workspaces where the restored title was a command name (e.g. "vim" or "git log"), the shell's first title report ("zsh" or the current directory) will be different from the restored title, so trimmed != restoredTitle evaluates to true, the guard is immediately cleared, and the restored title is still overwritten.
Per the PR description the intent is to suppress the initial generic shell title (e.g. "zsh") unconditionally, not only when it happens to match. A more robust approach would be to consume exactly one initial title event regardless of its value:
if let _ = restoredSessionProcessTitle {
// Consume the initial shell title event unconditionally; the restored
// title is already reflected in self.title / processTitle.
restoredSessionProcessTitle = nil
} else {
if self.title != trimmed {
self.title = trimmed
didMutate = true
}
if processTitle != trimmed {
processTitle = trimmed
}
}This would cover all restored-title shapes (directory-based, command-based, etc.) while still allowing subsequent title updates through normally.
| // Preserve the restored title so new shell process titles don't | ||
| // immediately overwrite it (see updatePanelTitle guard). | ||
| if snapshot.customTitle == nil, !snapshot.processTitle.isEmpty { | ||
| restoredSessionProcessTitle = snapshot.processTitle | ||
| } |
There was a problem hiding this comment.
Guard set for multi-panel workspaces but never cleared via normal path
restoredSessionProcessTitle is assigned unconditionally here for any workspace whose snapshot has customTitle == nil and a non-empty processTitle, regardless of how many panels the workspace had. However, in updatePanelTitle the guard is only examined when panels.count == 1. For a workspace restored with multiple panels, the guard is set but will never be evaluated or cleared through the normal updatePanelTitle path. It will persist until the workspace is deallocated or the user reduces panels to 1, at which point it could unexpectedly suppress an unrelated title update.
Consider gating the assignment on single-panel workspaces:
if snapshot.customTitle == nil, !snapshot.processTitle.isEmpty, snapshot.panels.count == 1 {
restoredSessionProcessTitle = snapshot.processTitle
}There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Sources/Workspace.swift`:
- Around line 6035-6061: The guard cleared by comparing trimmed !=
restoredSessionProcessTitle is too weak and will drop the restored title when
the relaunched shell reports a generic initial title (e.g., "zsh"); update the
logic in the panels.count == 1 && customTitle == nil branch to only clear
restoredSessionProcessTitle when there is a stronger signal of new shell
activity: instead of releasing the guard on simple inequality, require either
(a) an explicit shell-activity flag or transition (e.g., a newCommandStarted or
lastCommandTimestamp change you add/observe), or (b) a heuristic check that the
incoming trimmed title is meaningfully more informative than the snapshot (for
example not matching known shell names/prompts and containing whitespace or
command characters, or being longer than restoredSessionProcessTitle and not a
short shell identifier). Apply this change where restoredSessionProcessTitle,
trimmed, title and processTitle are handled so the guard only clears when the
new title indicates real new command activity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8953adc-bfda-4444-8cba-e7a5c0517412
📒 Files selected for processing (3)
Sources/AppDelegate.swiftSources/ContentView.swiftSources/Workspace.swift
| // If this is the only panel and no custom title, update workspace title. | ||
| // After session restore, suppress the first title update (the new shell | ||
| // reporting a generic title like "zsh") to preserve the restored title. | ||
| // Once a genuinely different title arrives, clear the guard and resume | ||
| // normal auto-updating. | ||
| if panels.count == 1, customTitle == nil { | ||
| if self.title != trimmed { | ||
| self.title = trimmed | ||
| didMutate = true | ||
| } | ||
| if processTitle != trimmed { | ||
| processTitle = trimmed | ||
| if let restoredTitle = restoredSessionProcessTitle { | ||
| // The new shell's initial title matches or is less informative | ||
| // than the restored title — keep the restored one. | ||
| if trimmed != restoredTitle { | ||
| restoredSessionProcessTitle = nil | ||
| if self.title != trimmed { | ||
| self.title = trimmed | ||
| didMutate = true | ||
| } | ||
| if processTitle != trimmed { | ||
| processTitle = trimmed | ||
| } | ||
| } | ||
| } else { | ||
| if self.title != trimmed { | ||
| self.title = trimmed | ||
| didMutate = true | ||
| } | ||
| if processTitle != trimmed { | ||
| processTitle = trimmed | ||
| } |
There was a problem hiding this comment.
trimmed != restoredTitle releases the guard too early.
Line 6044 treats any title that differs from the snapshot as a real post-restore update. If the snapshot was taken while the workspace title was a running command like npm test, the first prompt title from the relaunched shell (zsh) will clear the guard and overwrite the restored title immediately. That misses the "preserve restored title until a real new command appears" behavior this change is aiming for. Please key the release off a stronger signal than simple inequality, such as shell-activity state or another explicit "new command started" transition.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Sources/Workspace.swift` around lines 6035 - 6061, The guard cleared by
comparing trimmed != restoredSessionProcessTitle is too weak and will drop the
restored title when the relaunched shell reports a generic initial title (e.g.,
"zsh"); update the logic in the panels.count == 1 && customTitle == nil branch
to only clear restoredSessionProcessTitle when there is a stronger signal of new
shell activity: instead of releasing the guard on simple inequality, require
either (a) an explicit shell-activity flag or transition (e.g., a
newCommandStarted or lastCommandTimestamp change you add/observe), or (b) a
heuristic check that the incoming trimmed title is meaningfully more informative
than the snapshot (for example not matching known shell names/prompts and
containing whitespace or command characters, or being longer than
restoredSessionProcessTitle and not a short shell identifier). Apply this change
where restoredSessionProcessTitle, trimmed, title and processTitle are handled
so the guard only clears when the new title indicates real new command activity.
|
Thank you for this! Session restore title handling and duplicate-window prevention are covered on main in |
Summary
restoredSessionProcessTitleguard inWorkspacethat suppresses the initial shell title update, preserving the meaningful restored title until a genuinely different process title arrives.isRestorable = falseon all cmux windows (bothcreateMainWindowNSWindows and the primary SwiftUI WindowGroup window) to prevent macOS native state restoration from creating duplicate windows on relaunch — cmux manages its own session persistence viaSessionPersistenceStore.Test plan
CMUX_SKIP_ZIG_BUILD=1session.restore.startfires and restores 9 workspacesmainWindow.registerevent (no duplicate windows)🤖 Generated with Claude Code
Summary by cubic
Fixes session restore to keep workspace titles after relaunch and prevents macOS from opening duplicate windows. This removes confusing “zsh” titles and stops extra main windows from appearing.
restoredSessionProcessTitleinWorkspaceto ignore the first shell title update; cleared when a custom title is set or a different process title arrives.isRestorable = falseon the mainNSWindowand SwiftUIWindowGroupwindows; session state is handled bySessionPersistenceStore.Written for commit 7548a9e. Summary will update on new commits.
Summary by CodeRabbit