diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx
index 46984021756..a539626ad3d 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/V2OpenInMenuButton.tsx
@@ -18,16 +18,15 @@ import { HotkeyLabel, useHotkey, useHotkeyDisplay } from "renderer/hotkeys";
import { electronTrpc } from "renderer/lib/electron-trpc";
import { useV2ProjectDefaultApp } from "renderer/routes/_authenticated/hooks/useV2ProjectDefaultApp";
import { useThemeStore } from "renderer/stores";
+import { getV2WorktreeDisplayName } from "./utils/getV2WorktreeDisplayName";
interface V2OpenInMenuButtonProps {
worktreePath: string;
- branch: string;
projectId: string;
}
export function V2OpenInMenuButton({
worktreePath,
- branch,
projectId,
}: V2OpenInMenuButtonProps) {
const activeTheme = useThemeStore((state) => state.activeTheme);
@@ -57,6 +56,10 @@ export function V2OpenInMenuButton({
const showCopyPathShortcut = copyPathDisplay.text !== "Unassigned";
const isLoading = openInApp.isPending || copyPath.isPending;
const isDark = activeTheme?.type === "dark";
+ const displayName = useMemo(
+ () => getV2WorktreeDisplayName(worktreePath, projectId),
+ [worktreePath, projectId],
+ );
const handleOpenInEditor = useCallback(() => {
if (openInApp.isPending || copyPath.isPending) return;
@@ -107,9 +110,9 @@ export function V2OpenInMenuButton({
className="size-3.5 object-contain shrink-0"
/>
)}
- {branch && (
+ {displayName && (
- /{branch}
+ /{displayName}
)}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.test.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.test.ts
new file mode 100644
index 00000000000..0ee7425f869
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.test.ts
@@ -0,0 +1,45 @@
+import { describe, expect, test } from "bun:test";
+import { getV2WorktreeDisplayName } from "./getV2WorktreeDisplayName";
+
+describe("getV2WorktreeDisplayName", () => {
+ const projectId = "proj-abc";
+
+ test("returns the branch-at-creation directory name from a v2 worktree path", () => {
+ const path = `/home/user/.superset/worktrees/${projectId}/andrew/foo`;
+ expect(getV2WorktreeDisplayName(path, projectId)).toBe("andrew/foo");
+ });
+
+ test("preserves nested slashed branch names", () => {
+ const path = `/home/user/.superset/worktrees/${projectId}/feat/users/list`;
+ expect(getV2WorktreeDisplayName(path, projectId)).toBe("feat/users/list");
+ });
+
+ test("does NOT change with the workspace's current branch — the fix scenario from #3759", () => {
+ // Workspace was created from `andrew/foo`, so the directory is fixed there.
+ // After stacked PR navigation, `workspaces.branch` becomes `andrew/foo-2`.
+ // The displayed label must reflect the on-disk directory, not the live branch.
+ const persistedPath = `/home/user/.superset/worktrees/${projectId}/andrew/foo`;
+ const liveBranchAfterStackNavigation = "andrew/foo-2";
+
+ const display = getV2WorktreeDisplayName(persistedPath, projectId);
+
+ expect(display).toBe("andrew/foo");
+ expect(display).not.toBe(liveBranchAfterStackNavigation);
+ });
+
+ test("falls back to the basename if the projectId marker is absent", () => {
+ const path = "/some/other/location/my-branch";
+ expect(getV2WorktreeDisplayName(path, projectId)).toBe("my-branch");
+ });
+
+ test("handles Windows-style separators in the fallback", () => {
+ const path = "C:\\Users\\me\\some\\place\\my-branch";
+ expect(getV2WorktreeDisplayName(path, projectId)).toBe("my-branch");
+ });
+
+ test("returns the original string if no separator is present", () => {
+ expect(getV2WorktreeDisplayName("standalone", projectId)).toBe(
+ "standalone",
+ );
+ });
+});
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.ts
new file mode 100644
index 00000000000..d0ace8fec63
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/getV2WorktreeDisplayName.ts
@@ -0,0 +1,20 @@
+// V2 worktrees live at `/.superset/worktrees//`.
+// The directory name is fixed at create time; `workspaces.branch` drifts as HEAD
+// moves (git status sync, AI rename, manual checkout). Derive the label from the
+// persisted path so it always matches the on-disk directory.
+export function getV2WorktreeDisplayName(
+ worktreePath: string,
+ projectId: string,
+): string {
+ const marker = `worktrees/${projectId}/`;
+ const idx = worktreePath.indexOf(marker);
+ if (idx >= 0) {
+ const tail = worktreePath.slice(idx + marker.length);
+ if (tail.length > 0) return tail;
+ }
+ const lastSep = Math.max(
+ worktreePath.lastIndexOf("/"),
+ worktreePath.lastIndexOf("\\"),
+ );
+ return lastSep >= 0 ? worktreePath.slice(lastSep + 1) : worktreePath;
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/index.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/index.ts
new file mode 100644
index 00000000000..2474f3aa930
--- /dev/null
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2OpenInMenuButton/utils/getV2WorktreeDisplayName/index.ts
@@ -0,0 +1 @@
+export { getV2WorktreeDisplayName } from "./getV2WorktreeDisplayName";
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx
index eab12edd4d6..b0b6fe61f96 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx
@@ -26,7 +26,6 @@ export function V2WorkspaceOpenInButton({
.where(({ workspaces }) => eq(workspaces.id, workspaceId))
.select(({ workspaces, hosts }) => ({
id: workspaces.id,
- branch: workspaces.branch,
projectId: workspaces.projectId,
hostMachineId: hosts?.machineId ?? null,
})),
@@ -55,7 +54,6 @@ export function V2WorkspaceOpenInButton({
return (