From c11fbc411812f1e468d32f7f382de823a31a9c79 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 17:47:20 -0400 Subject: [PATCH 1/2] test: keep missing restore cwd in its own path ancestry --- ...erminalWorkingDirectoryFallbackTests.swift | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift b/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift index 2ce4c0470685..e8ff3c86b8d6 100644 --- a/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift +++ b/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift @@ -9,6 +9,49 @@ import Testing @MainActor @Suite struct WorkspaceTerminalWorkingDirectoryFallbackTests { + @Test func missingLocalRestoreDirectoryUsesItsNearestExistingParent() throws { + let root = FileManager.default.temporaryDirectory + .appendingPathComponent("cmux-missing-cwd-\(UUID().uuidString)") + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: root) } + + let missingSavedDirectory = root + .appendingPathComponent("deleted-worktree", isDirectory: true) + .appendingPathComponent("project", isDirectory: true) + .path + let selectedWorkspaceDirectory = root + .appendingPathComponent("selected-workspace", isDirectory: true) + try FileManager.default.createDirectory(at: selectedWorkspaceDirectory, withIntermediateDirectories: true) + + let resolved = RemoteTerminalWorkingDirectoryResolver.resolve( + requested: missingSavedDirectory, + preserveExact: false, + rescued: nil, + panelDirectory: missingSavedDirectory, + requestedPanelDirectory: nil, + remoteInitialDirectory: nil, + currentDirectory: selectedWorkspaceDirectory.path + ) + + #expect(resolved == root.path) + #expect(resolved != selectedWorkspaceDirectory.path) + } + + @Test func remoteRestorePreservesAnExactMissingDirectory() { + let missingRemoteDirectory = "/home/cmux/deleted-worktree/project" + let resolved = RemoteTerminalWorkingDirectoryResolver.resolve( + requested: missingRemoteDirectory, + preserveExact: true, + rescued: nil, + panelDirectory: nil, + requestedPanelDirectory: nil, + remoteInitialDirectory: nil, + currentDirectory: "/Users/selected" + ) + + #expect(resolved == missingRemoteDirectory) + } + @Test func newTerminalSurfaceFallsBackToRequestedWorkingDirectoryWhenReportedDirectoryIsStale() throws { let workspace = Workspace() let sourcePaneId = try #require( From ce6425906833fa630fc8490543bcd375a866a66a Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 30 Sep 2026 17:48:49 -0400 Subject: [PATCH 2/2] Keep restored terminals out of another workspace cwd --- ...moteTerminalWorkingDirectoryResolver.swift | 40 ++++++++++++++++--- ...erminalWorkingDirectoryFallbackTests.swift | 13 ++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Sources/RemoteTerminalWorkingDirectoryResolver.swift b/Sources/RemoteTerminalWorkingDirectoryResolver.swift index 4d5dd6639961..919c8532e159 100644 --- a/Sources/RemoteTerminalWorkingDirectoryResolver.swift +++ b/Sources/RemoteTerminalWorkingDirectoryResolver.swift @@ -1,3 +1,5 @@ +import Foundation + enum RemoteTerminalWorkingDirectoryResolver { static func normalized(_ workingDirectory: String?, preserveExact: Bool) -> String? { guard let workingDirectory else { return nil } @@ -16,11 +18,20 @@ enum RemoteTerminalWorkingDirectoryResolver { remoteInitialDirectory: String?, currentDirectory: String? ) -> String? { - if let requested = normalized(requested, preserveExact: preserveExact) { - return requested - } - if let rescued { - return rescued + if preserveExact { + if let requested = normalized(requested, preserveExact: true) { return requested } + if let rescued = normalized(rescued, preserveExact: true) { return rescued } + } else { + // A deleted local worktree must never fall through to the + // selected workspace's directory. Keep the restore in the saved + // path's ancestry, where a resumed agent cannot silently target a + // different repository. + if let requested = normalized(requested, preserveExact: false), + nearestExistingDirectory(requested) == requested { + return requested + } + if let rescued = nearestExistingDirectory(rescued) { return rescued } + if let requested = nearestExistingDirectory(requested) { return requested } } let candidates = [ panelDirectory, @@ -31,6 +42,23 @@ enum RemoteTerminalWorkingDirectoryResolver { if preserveExact { return candidates.lazy.compactMap { normalized($0, preserveExact: true) }.first } - return TerminalWorkingDirectoryResolver.firstAvailable(candidates) + if let candidate = candidates.lazy.compactMap(nearestExistingDirectory).first { + return candidate + } + return FileManager.default.homeDirectoryForCurrentUser.path + } + + private static func nearestExistingDirectory(_ value: String?) -> String? { + guard let normalized = normalized(value, preserveExact: false) else { return nil } + var url = URL(fileURLWithPath: normalized, isDirectory: true) + while true { + var isDirectory: ObjCBool = false + if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), isDirectory.boolValue { + return url.path + } + let parent = url.deletingLastPathComponent() + guard parent.path != url.path else { return nil } + url = parent + } } } diff --git a/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift b/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift index e8ff3c86b8d6..85e5c4c1cd3c 100644 --- a/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift +++ b/cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift @@ -35,6 +35,19 @@ import Testing #expect(resolved == root.path) #expect(resolved != selectedWorkspaceDirectory.path) + + let rescuedDirectory = root.appendingPathComponent("rescued-agent", isDirectory: true) + try FileManager.default.createDirectory(at: rescuedDirectory, withIntermediateDirectories: true) + let rescued = RemoteTerminalWorkingDirectoryResolver.resolve( + requested: missingSavedDirectory, + preserveExact: false, + rescued: rescuedDirectory.path, + panelDirectory: missingSavedDirectory, + requestedPanelDirectory: nil, + remoteInitialDirectory: nil, + currentDirectory: selectedWorkspaceDirectory.path + ) + #expect(rescued == rescuedDirectory.path) } @Test func remoteRestorePreservesAnExactMissingDirectory() {