Skip to content

Fix agent resume post-exit cwd - #5421

Closed
austinywang wants to merge 3 commits into
mainfrom
issue-5391-exit-root-cwd
Closed

austinywang wants to merge 3 commits into
mainfrom
issue-5391-exit-root-cwd

Conversation

@austinywang

@austinywang austinywang commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5391

Summary

  • add a behavior regression that reproduces the return-shell cwd bug through generated launcher scripts
  • separate the resumed agent command cwd from the post-exit return-shell cwd
  • return the outer login shell to the saved session/surface cwd when available, otherwise $HOME, before exec -l

Two-commit proof

  1. test: cover agent resume return cwd adds the failing regression only
  2. fix: restore cwd after agent resume exits implements the shared launcher fix

Verification

  • Reproduced directly with zsh before the fix: child resume command landed in the project directory while the outer shell stayed at / before exec -l.
  • Local tests/build/reload intentionally not run per task constraints; CI should provide the red/green proof.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Changes generated zsh startup/restore scripts and cwd selection across workspace restore and agent resume paths; behavior is well-covered by new tests but affects real shell state on exit.

Overview
Fixes post-exit shell placement after agent/surface resume by splitting the cwd used inside the resumed command from the cwd chain used when the outer login shell returns before exec -l.

Return-shell scripts now take returnWorkingDirectories instead of a single workingDirectory. TerminalStartupReturnShellScript emits a chained cd (normalized, deduped candidates, then $HOME) rather than one optional directory or silence. Shared helpers normalizedTerminalWorkingDirectory / firstNormalizedTerminalWorkingDirectory centralize trimming and “first non-empty” selection.

Call sites thread ordered candidates through agent resume launchers, surface binding launchers, and Workspace restore (binding → terminal → agent → snapshot). Agents with .ignore cwd still resume without forcing project cd in the child, but the outer shell can still return via the explicit candidate list.

Tests: new TerminalStartupReturnShellScriptTests (instrumented zsh scripts, .ignore agents, empty/stale binding cwd); existing auto-resume test updated for $HOME fallback.

Reviewed by Cursor Bugbot for commit b71d247. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes a regression where the outer login shell could stay at / or the launcher cwd after an agent resume. We now always return the outer shell to the saved session directory via an ordered, normalized cwd list with a $HOME fallback, without changing the resumed command’s cwd. Fixes #5391.

  • Bug Fixes
    • Split the resumed command cwd from the post-exit return-shell cwd.
    • TerminalStartupReturnShellScript now takes returnWorkingDirectories, builds a normalized, de-duped cd chain, and always appends $HOME before exec -l.
    • .ignore agents: the resumed command runs in the current dir; the outer shell returns via the candidate chain/$HOME or explicit overrides.
    • Workspace and binding launchers pass an ordered return list (binding → terminal → agent → snapshot); helpers normalizedTerminalWorkingDirectory / firstNormalizedTerminalWorkingDirectory centralize trimming/selection.
    • Added TerminalStartupReturnShellScriptTests and updated tests to cover $HOME default when no cwd is saved, runtime fallback when preferred cwd is missing, .ignore behavior, and workspace restore when binding cwd is empty/stale.

Written for commit b71d247. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Resume now builds a prioritized list of return directories (normalized, de-duplicated) and tries each with cd, falling back to HOME; agent ignore policy can suppress returning to a directory.
  • Tests

    • New test suite validating return-script generation and runtime fallback selection (including HOME fallback) across resume scenarios; updated expectations for return-to-launch behavior.
  • Chores

    • Project config updated to include the new tests.

@vercel

vercel Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 5, 2026 5:51am
cmux-staging Building Building Preview, Comment Jun 5, 2026 5:51am

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Threads a normalized optional returnWorkingDirectory through workspace startup, surface/agent resume launcher generation, and return-shell script creation; the return script now emits a prioritized cd chain (primary → fallback → HOME). Tests and Xcode project entries validate the new behavior.

Changes

Resume Launcher Working Directory Fallback

Layer / File(s) Summary
Working-directory normalization helper
Sources/RestorableAgentSession.swift
Adds normalizedTerminalWorkingDirectory(_:) and firstNormalizedTerminalWorkingDirectory(_:), and refactors TerminalStartupWorkingDirectoryPrefix.normalized(_:) to use them.
Agent resume: saved return directory + launcher parameters
Sources/RestorableAgentSession.swift
SessionRestorableAgentSnapshot.resumeStartupCommand now computes a pre-normalized resume working directory and accepts returnWorkingDirectories: [String?]; AgentResumeScriptStore.writeLauncherScript accepts returnWorkingDirectories and forwards them when present.
Return-shell: signature and prioritized cd generation
Sources/SessionPersistence.swift
TerminalStartupReturnShellScript.commandThenReturnLines now accepts returnWorkingDirectories: [String?] and emits a normalized, de-duplicated prioritized cd chain that attempts each candidate then $HOME.
Surface resume wiring: parameter threading
Sources/SessionPersistence.swift
SurfaceResumeBindingSnapshot.startupCommandWithLauncherScript and SurfaceResumeBindingScriptStore.writeLauncherScript gain returnWorkingDirectories and pass [binding.cwd] + returnWorkingDirectories into the return-shell generator for returnToLoginShell.
Workspace: compute bindingReturnWorkingDirectories and forward
Sources/Workspace.swift
Startup-launch boundary accepts returnWorkingDirectories; computes bindingReturnWorkingDirectories from binding cwd → terminal working dir → agent working dir → snapshot dir; threads candidates into restored binding launch and uses firstNormalizedTerminalWorkingDirectory to compute savedWorkingDirectory passed to restorable agent resume.
Tests and project integration
cmuxTests/TerminalStartupReturnShellScriptTests.swift, cmuxTests/AgentSessionAutoResumeSettingsTests.swift, cmux.xcodeproj/project.pbxproj
Adds TerminalStartupReturnShellScriptTests with script-structure assertions and a TemporaryScriptFixture for runtime instrumentation; updates an existing auto-resume test expectation to require HOME fallback; wires the new tests into the Xcode project.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through code to mend the trail,
A chain of cd's so shells won't fail,
Preferred then fallback, then HOME at last—
A tidy return, no errors cast. 🏡


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error Two pure helper functions in RestorableAgentSession.swift lack nonisolated: normalizedTerminalWorkingDirectory and firstNormalizedTerminalWorkingDirectory, causing unnecessary MainActor coupling. Mark both normalizedTerminalWorkingDirectory and firstNormalizedTerminalWorkingDirectory with nonisolated keyword in RestorableAgentSession.swift.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix agent resume post-exit cwd' directly and clearly summarizes the main change: fixing the working directory behavior after an agent resumes and exits.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Blocking Runtime ✅ Passed No blocking primitives added in production code. Changes are deterministic string/directory normalization and parameter threading. Test code uses Process.waitUntilExit() which is allowed scaffolding.
Cmux No Hacky Sleeps ✅ Passed All changes are Swift files and Xcode config. Rule scope excludes Swift (covered separately), applies only to TypeScript/JavaScript/shell/non-Swift build scripts—none present here.
Cmux Algorithmic Complexity ✅ Passed Code uses O(m²) contains checks for deduplication (SessionPersistence.swift:1310) but only on tiny fixed arrays (max 5 elements). Acceptable per rule: tiny fixed-size collections with explicit bounds.
Cmux Swift Concurrency ✅ Passed PR introduces only synchronous helper methods and parameter additions; no legacy dispatch queues, Combine patterns, completion-handler APIs, or fire-and-forget Tasks with lifecycle.
Cmux Swift @Concurrent ✅ Passed All Swift changes use synchronous functions only; no async functions, @concurrent annotations, or nonisolated async patterns introduced that would violate swift-concurrent-annotation rules.
Cmux Swift File And Package Boundaries ✅ Passed Small focused bug fix adding 26/20/17 net lines to existing files under 250-line threshold, no oversized files or mixed responsibilities detected.
Cmux Swift Logging ✅ Passed Production Swift changes introduce no logging violations: no print/debugPrint/dump/NSLog in app code, no unguarded file logging, proper nonisolated enum declaration, test-only new code.
Cmux User-Facing Error Privacy ✅ Passed PR adds no user-facing errors or alerts. Changes are internal shell script generation and path normalization. No vendor names, credentials, or sensitive env vars exposed to users.
Cmux Full Internationalization ✅ Passed PR adds only implementation logic (helper functions, parameter threading, shell script generation) and tests. No new user-facing strings, localization keys, xcstrings, or plist entries are added.
Cmux Swiftui State Layout ✅ Passed PR changes are to data/model layer helper functions and method signatures. No new @Published/@Observable/@State decorators, no GeometryReader/LazyVStack changes, no render-time state mutations found.
Cmux Architecture Rethink ✅ Passed Separates resumed agent cwd from post-exit shell return cwd via immutable parameter threading with clear invariant and single state ownership. No timing repairs, observers, or side channels.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup declarations in source files. Test file is test-only fixture (exempt). Changes focus on working directory handling.
Description check ✅ Passed The PR description includes all required sections: Summary (what changed and why), Testing (verification approach), Two-commit structure, and a Checklist with partially completed items.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5391-exit-root-cwd

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes issue #5391 by decoupling the resumed agent command's cd from the outer login shell's post-exit directory. The generated launcher scripts now build a normalized, de-duplicated candidate list and try each in order with ||, always falling back to $HOME.

  • TerminalStartupReturnShellScript.commandThenReturnLines now takes returnWorkingDirectories: [String?] and always emits a cd chain ending in cd -- \"${HOME}\", replacing the old single-path-or-nothing guard.
  • Workspace builds two distinct candidate lists (bindingReturnWorkingDirectories and agentReturnWorkingDirectories) threaded through firstNormalizedTerminalWorkingDirectory so whitespace-only stored paths are treated as absent.
  • New TerminalStartupReturnShellScriptTests suite with real zsh execution covers the HOME fallback, stale-path runtime fallback, .ignore-policy agent, and workspace restore scenarios.

Confidence Score: 5/5

Safe to merge — changes are confined to launcher script generation and the return-cwd wiring, fully covered by real zsh execution tests.

The fix correctly splits the resumed command's own cd prefix from the outer shell's post-exit directory, normalization handles nil/empty/whitespace-only paths, and the always-emit-HOME fallback closes the landing regression. Integration tests execute real generated scripts and assert outer-pwd-before-exec.

No files require special attention.

Important Files Changed

Filename Overview
Sources/SessionPersistence.swift Core fix: commandThenReturnLines now always emits a cd chain with all normalized candidates followed by cd "${HOME}". SurfaceResumeBindingScriptStore prepends binding.cwd to the caller-supplied list before de-duplication.
Sources/RestorableAgentSession.swift Adds normalizedTerminalWorkingDirectory/firstNormalizedTerminalWorkingDirectory as module-scoped free functions, separates resumeCommandWorkingDirectory from returnWorkingDirectories in resumeStartupCommand, and threads the list through AgentResumeScriptStore.
Sources/Workspace.swift Builds two distinct return-directory candidate lists and threads them to the appropriate launcher paths. savedWorkingDirectory now uses firstNormalizedTerminalWorkingDirectory so whitespace-only binding cwds are skipped.
cmuxTests/TerminalStartupReturnShellScriptTests.swift New integration test suite executing real zsh scripts to verify outer-pwd-before-exec values across HOME fallback, stale-path, .ignore-policy, and workspace restore scenarios.
cmuxTests/AgentSessionAutoResumeSettingsTests.swift Updated to match the new returnWorkingDirectories API and the changed assertion that bare scripts now emit cd "${HOME}" rather than no cd at all.
cmux.xcodeproj/project.pbxproj Adds TerminalStartupReturnShellScriptTests.swift to the file reference list and the test target's Sources build phase.

Reviews (7): Last reviewed commit: "Merge branch 'main' of https://github.co..." | Re-trigger Greptile

Comment on lines +80 to +107
let temp = try TemporaryScriptFixture()
defer { temp.cleanup() }
let binding = SurfaceResumeBindingSnapshot(
name: "Issue 5391 Binding",
kind: "codex",
command: #"/bin/zsh -fc 'printf "child-pwd=%s\n" "$PWD"'"#,
cwd: " ",
source: "agent-hook",
autoResume: true,
updatedAt: 123
)

let startupCommand = try #require(binding.startupCommandWithLauncherScript(
fileManager: temp.fileManager,
temporaryDirectory: temp.root
))
let scriptContents = try String(
contentsOfFile: launcherScriptPath(from: startupCommand),
encoding: .utf8
)

try expectGeneratedScript(scriptContents, returnsTo: .home)
let output = try temp.runInstrumentedScript(scriptContents)

#expect(output.lines.contains("child-pwd=/"))
#expect(output.lines.contains("outer-pwd-before-exec=\(temp.homeDirectory.path)"))
#expect(!output.lines.contains("outer-pwd-before-exec=/"))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Surface-binding test doesn't cover the returnWorkingDirectory fallback path

surfaceResumeBindingLauncherFallsBackToHomeForEmptyCwd calls startupCommandWithLauncherScript without passing returnWorkingDirectory, so fallbackWorkingDirectory inside commandThenReturnLines is always nil. The test only confirms the pure $HOME branch. The production path through Workspace.surfaceResumeStartupLaunch always passes bindingReturnWorkingDirectory (e.g. snapshot.terminal?.workingDirectory), meaning that when binding.cwd is empty but a snapshot directory exists, the outer shell should navigate there — not to $HOME. A complementary test asserting returnsTo: .literal(snapshotDir) when returnWorkingDirectory is non-nil would give full branch coverage for normalized(workingDirectory) ?? normalized(fallbackWorkingDirectory).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/RestorableAgentSession.swift`:
- Line 717: The savedReturnWorkingDirectory selection uses workingDirectory ??
launchCommand?.workingDirectory which treats whitespace-only strings as valid;
normalize workingDirectory first (trim whitespace/newlines and treat empty after
trimming as nil) before falling back to launchCommand?.workingDirectory so a
blank persisted cwd does not suppress launchCommand?.workingDirectory — update
the assignment for savedReturnWorkingDirectory in RestorableAgentSession.swift
to use the trimmed/emptiness-checked workingDirectory when deciding to fall back
to launchCommand?.workingDirectory.

In `@Sources/SessionPersistence.swift`:
- Around line 1303-1308: The generated shell snippet currently only tries to cd
into the preferred directory and gives up on failure; update the branch that
builds the line (the block using normalized(workingDirectory) ??
normalized(fallbackWorkingDirectory), TerminalStartupShellQuoting.singleQuoted
and lines.append) so the appended shell expression chains runtime fallbacks:
attempt cd to the quoted primary returnWorkingDirectory, if that fails try the
quoted fallbackWorkingDirectory, and if that also fails fall back to cd --
"${HOME}" (all failures suppressed with 2>/dev/null and || to continue); in
short, change the single cd command produced by
TerminalStartupShellQuoting.singleQuoted into a chained cd sequence that tries
primary -> fallback -> $HOME at runtime.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8be45352-785a-4ed9-b980-2726bc75569a

📥 Commits

Reviewing files that changed from the base of the PR and between 087309a and cc2658d.

📒 Files selected for processing (6)
  • Sources/RestorableAgentSession.swift
  • Sources/SessionPersistence.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift
  • cmuxTests/TerminalStartupReturnShellScriptTests.swift

Comment thread Sources/RestorableAgentSession.swift Outdated
Comment thread Sources/SessionPersistence.swift Outdated
Comment thread Sources/Workspace.swift Outdated
Comment on lines +1625 to +1629
let bindingReturnWorkingDirectory =
effectiveResumeBindingForStartup?.cwd
?? snapshot.terminal?.workingDirectory
?? restorableAgent?.workingDirectory
?? snapshot.directory

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The bindingReturnWorkingDirectory cascade uses the raw, un-normalized value of effectiveResumeBindingForStartup?.cwd. When cwd is a whitespace-only string (e.g. " ") — a case the new tests explicitly exercise — Swift's ?? operator sees it as non-nil and short-circuits, so snapshot.terminal?.workingDirectory, restorableAgent?.workingDirectory, and snapshot.directory are never considered. normalizedTerminalWorkingDirectory then trims the whitespace value to nil inside commandThenReturnLines, and the outer shell falls through to $HOME rather than the terminal's saved directory.

Suggested change
let bindingReturnWorkingDirectory =
effectiveResumeBindingForStartup?.cwd
?? snapshot.terminal?.workingDirectory
?? restorableAgent?.workingDirectory
?? snapshot.directory
let bindingReturnWorkingDirectory =
normalizedTerminalWorkingDirectory(effectiveResumeBindingForStartup?.cwd)
?? snapshot.terminal?.workingDirectory
?? restorableAgent?.workingDirectory
?? snapshot.directory

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/RestorableAgentSession.swift (1)

717-737: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

resumeStartupCommand still can't take a distinct return cwd.

workingDirectory on SessionRestorableAgentSnapshot is the resume/fork cwd, but this method also reuses it (or launchCommand?.workingDirectory) as the post-exit return target. That means the agent launcher path still cannot restore the outer shell to the saved panel/session cwd when those differ — most obviously for cwd == .ignore, where the correct return dir must come from the surface snapshot, not the resume snapshot.

Suggested direction
-    func resumeStartupCommand(
-        fileManager: FileManager = .default,
-        temporaryDirectory: URL = FileManager.default.temporaryDirectory
-    ) -> String? {
-        let savedReturnWorkingDirectory = normalizedTerminalWorkingDirectory(workingDirectory)
+    func resumeStartupCommand(
+        fileManager: FileManager = .default,
+        temporaryDirectory: URL = FileManager.default.temporaryDirectory,
+        returnWorkingDirectory: String? = nil
+    ) -> String? {
+        let savedReturnWorkingDirectory = normalizedTerminalWorkingDirectory(returnWorkingDirectory)
+            ?? normalizedTerminalWorkingDirectory(workingDirectory)
             ?? normalizedTerminalWorkingDirectory(launchCommand?.workingDirectory)

Then thread the saved surface/panel cwd into this API from the restore path instead of recomputing it locally.

Based on learnings: SessionEntry.resumeWorkingDirectory is the single source of truth for the working directory used in registered-agent resume commands, and registrations with cwd: .ignore set it to nil, suppressing both the cwd guard in the resume command and the terminal working directory at placement time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/RestorableAgentSession.swift` around lines 717 - 737, The
resumeStartupCommand currently recomputes the "return" cwd from
SessionRestorableAgentSnapshot. Replace that recomputation by accepting/using
the single source of truth (the saved surface/panel cwd provided by the restore
path — e.g. SessionEntry.resumeWorkingDirectory or an explicit parameter) and
pass that value into AgentResumeScriptStore.writeLauncherScript's
workingDirectory/fallbackWorkingDirectory arguments instead of using
normalizedTerminalWorkingDirectory(workingDirectory) or
launchCommand?.workingDirectory; ensure registrations with cwd == .ignore map to
nil so the resume command's internal cd guard behavior remains unchanged (update
resumeStartupCommand signature to take the returnWorkingDirectory or read
SessionEntry.resumeWorkingDirectory and use resumeCommand/registration?.cwd
logic accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/TerminalStartupReturnShellScriptTests.swift`:
- Around line 192-197: Replace the non-fatal assertions in
shellSingleQuotedTokenValue(_:) with a fatal precondition: change the two
`#expect` checks for token.hasPrefix("'") and token.hasSuffix("'") to `#require` (or
another precondition API) so the method aborts on violation before calling
token.dropFirst().dropLast(); include a short message in each `#require` to
indicate the expectation (e.g., "token must start with '\''" and "token must end
with '\''") to make failures clear.

---

Outside diff comments:
In `@Sources/RestorableAgentSession.swift`:
- Around line 717-737: The resumeStartupCommand currently recomputes the
"return" cwd from SessionRestorableAgentSnapshot. Replace that recomputation by
accepting/using the single source of truth (the saved surface/panel cwd provided
by the restore path — e.g. SessionEntry.resumeWorkingDirectory or an explicit
parameter) and pass that value into AgentResumeScriptStore.writeLauncherScript's
workingDirectory/fallbackWorkingDirectory arguments instead of using
normalizedTerminalWorkingDirectory(workingDirectory) or
launchCommand?.workingDirectory; ensure registrations with cwd == .ignore map to
nil so the resume command's internal cd guard behavior remains unchanged (update
resumeStartupCommand signature to take the returnWorkingDirectory or read
SessionEntry.resumeWorkingDirectory and use resumeCommand/registration?.cwd
logic accordingly).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e5beab6f-372e-47d3-b8ad-dd492062c9fa

📥 Commits

Reviewing files that changed from the base of the PR and between cc2658d and c5580c5.

📒 Files selected for processing (6)
  • Sources/RestorableAgentSession.swift
  • Sources/SessionPersistence.swift
  • Sources/Workspace.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift
  • cmuxTests/TerminalStartupReturnShellScriptTests.swift

Comment thread cmuxTests/TerminalStartupReturnShellScriptTests.swift
Comment thread Sources/SessionPersistence.swift

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f3d7713. Configure here.

Comment thread Sources/Workspace.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/SessionPersistence.swift (1)

1285-1356: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract the return-shell/launcher code into its own Swift file.

This change adds more restore-shell behavior to a file that already owns snapshot schema, approval persistence, and crypto/keychain logic. Keeping TerminalStartupReturnShellScript and SurfaceResumeBindingScriptStore here makes an already over-budget file harder to reason about and test.

As per coding guidelines, “Flag Swift production files that exceed 400 lines without a clear single responsibility, or exceed 800 lines even with mostly coherent responsibility” and “One major type per file.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/SessionPersistence.swift` around lines 1285 - 1356, The file has
grown beyond a single responsibility; extract the resume-shell/launcher logic
into a new Swift source (e.g., TerminalStartupReturnShellScript.swift) by moving
the TerminalStartupReturnShellScript type and the
SurfaceResumeBindingScriptStore type + their private helpers
(returnWorkingDirectoryLine, normalized, commandThenReturnLines,
zshIntegrationReentryLines, safeFilenamePrefix references used) into the new
file; preserve existing access control (private/internal) or raise to internal
if they are referenced outside, add any needed imports (Foundation/FileManager),
update any call sites to the moved types (no API changes), and run tests to
ensure symbols resolve and no visibility regressions occur.
♻️ Duplicate comments (1)
Sources/RestorableAgentSession.swift (1)

846-852: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the return-directory list before deciding whether to fall back.

returnWorkingDirectories.isEmpty only catches a literally empty array. A value like [nil] or [" "] still bypasses workingDirectory, and TerminalStartupReturnShellScript then normalizes those entries away and falls straight through to $HOME. That makes the new “ignore whitespace-only paths” behavior inconsistent for callers that pass placeholder entries.

Suggested fix
             if returnToLoginShell {
+                let effectiveReturnWorkingDirectories =
+                    firstNormalizedTerminalWorkingDirectory(returnWorkingDirectories) == nil
+                    ? returnWorkingDirectories + [workingDirectory]
+                    : returnWorkingDirectories
                 lines.append(contentsOf: TerminalStartupReturnShellScript.commandThenReturnLines(
                     command: command,
-                    returnWorkingDirectories: returnWorkingDirectories.isEmpty
-                        ? [workingDirectory]
-                        : returnWorkingDirectories
+                    returnWorkingDirectories: effectiveReturnWorkingDirectories
                 ))
             } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/RestorableAgentSession.swift` around lines 846 - 852, Normalize and
filter returnWorkingDirectories before checking emptiness: compute a normalized
array by compactMap-ing/removing nils, trimming whitespace/newlines, and
filtering out empty strings, then use that normalized array in the conditional
(i.e. use [workingDirectory] when the normalized array is empty, otherwise pass
the normalized array into
TerminalStartupReturnShellScript.commandThenReturnLines). Update the code around
the returnToLoginShell block that references returnWorkingDirectories and
workingDirectory (RestorableAgentSession.swift /
TerminalStartupReturnShellScript.commandThenReturnLines) to use this normalized
list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/SessionPersistence.swift`:
- Around line 1285-1356: The file has grown beyond a single responsibility;
extract the resume-shell/launcher logic into a new Swift source (e.g.,
TerminalStartupReturnShellScript.swift) by moving the
TerminalStartupReturnShellScript type and the SurfaceResumeBindingScriptStore
type + their private helpers (returnWorkingDirectoryLine, normalized,
commandThenReturnLines, zshIntegrationReentryLines, safeFilenamePrefix
references used) into the new file; preserve existing access control
(private/internal) or raise to internal if they are referenced outside, add any
needed imports (Foundation/FileManager), update any call sites to the moved
types (no API changes), and run tests to ensure symbols resolve and no
visibility regressions occur.

---

Duplicate comments:
In `@Sources/RestorableAgentSession.swift`:
- Around line 846-852: Normalize and filter returnWorkingDirectories before
checking emptiness: compute a normalized array by compactMap-ing/removing nils,
trimming whitespace/newlines, and filtering out empty strings, then use that
normalized array in the conditional (i.e. use [workingDirectory] when the
normalized array is empty, otherwise pass the normalized array into
TerminalStartupReturnShellScript.commandThenReturnLines). Update the code around
the returnToLoginShell block that references returnWorkingDirectories and
workingDirectory (RestorableAgentSession.swift /
TerminalStartupReturnShellScript.commandThenReturnLines) to use this normalized
list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6cb7497-ee2c-4026-8586-226cff62bc4e

📥 Commits

Reviewing files that changed from the base of the PR and between f3d7713 and cf17278.

📒 Files selected for processing (5)
  • Sources/RestorableAgentSession.swift
  • Sources/SessionPersistence.swift
  • Sources/Workspace.swift
  • cmuxTests/AgentSessionAutoResumeSettingsTests.swift
  • cmuxTests/TerminalStartupReturnShellScriptTests.swift

…-5391-exit-root-cwd

# Conflicts:
#	cmux.xcodeproj/project.pbxproj
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — b71d2470 Deployed Jun 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exiting a Claude/agent session leaves the surface shell in / (root) instead of the session directory

3 participants