From f0dd7a0cc37fc33e1be7d7e7a7f6b5099795bcc5 Mon Sep 17 00:00:00 2001 From: Tim Glaser Date: Mon, 18 May 2026 23:12:31 +0000 Subject: [PATCH 1/2] fix(sessions): strip remote workspace prefix from file paths in chat For cloud tasks the local workspace cwd is empty, so FileMentionChip fell back to rendering the full sandbox path (/tmp/workspace/repos///...). Derive the conventional remote root from `task.repository` when there is no local cwd, so the chip shows just the repo-relative directory. Generated-By: PostHog Code Task-Id: 1b29c9c1-769d-4c98-a3c1-04c703482115 --- .../session-update/FileMentionChip.tsx | 4 +- .../sessions/hooks/useDisplayRepoPath.test.ts | 74 +++++++++++++++++++ .../sessions/hooks/useDisplayRepoPath.ts | 21 ++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.test.ts create mode 100644 apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts diff --git a/apps/code/src/renderer/features/sessions/components/session-update/FileMentionChip.tsx b/apps/code/src/renderer/features/sessions/components/session-update/FileMentionChip.tsx index 890aa6068..c6d350bdc 100644 --- a/apps/code/src/renderer/features/sessions/components/session-update/FileMentionChip.tsx +++ b/apps/code/src/renderer/features/sessions/components/session-update/FileMentionChip.tsx @@ -1,6 +1,6 @@ import { FileIcon } from "@components/ui/FileIcon"; import { usePanelLayoutStore } from "@features/panels"; -import { useCwd } from "@features/sidebar/hooks/useCwd"; +import { useDisplayRepoPath } from "@features/sessions/hooks/useDisplayRepoPath"; import { useTaskStore } from "@features/tasks/stores/taskStore"; import { useWorkspace } from "@features/workspace/hooks/useWorkspace"; import { Flex, Text } from "@radix-ui/themes"; @@ -33,7 +33,7 @@ export const FileMentionChip = memo(function FileMentionChip({ filePath, }: FileMentionChipProps) { const taskId = useTaskStore((s) => s.selectedTaskId); - const repoPath = useCwd(taskId ?? ""); + const repoPath = useDisplayRepoPath(taskId ?? undefined); const workspace = useWorkspace(taskId ?? undefined); const openFileInSplit = usePanelLayoutStore((s) => s.openFileInSplit); diff --git a/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.test.ts b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.test.ts new file mode 100644 index 000000000..48ac87791 --- /dev/null +++ b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.test.ts @@ -0,0 +1,74 @@ +import { renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mockUseCwd = vi.hoisted(() => vi.fn((): string | undefined => undefined)); +const mockUseTasks = vi.hoisted(() => + vi.fn((): { data: Array<{ id: string; repository?: string | null }> } => ({ + data: [], + })), +); + +vi.mock("@features/sidebar/hooks/useCwd", () => ({ useCwd: mockUseCwd })); +vi.mock("@features/tasks/hooks/useTasks", () => ({ useTasks: mockUseTasks })); + +import { useDisplayRepoPath } from "./useDisplayRepoPath"; + +describe("useDisplayRepoPath", () => { + beforeEach(() => { + mockUseCwd.mockReturnValue(undefined); + mockUseTasks.mockReturnValue({ data: [] }); + }); + + it("returns local cwd when available (local task)", () => { + mockUseCwd.mockReturnValue("/Users/me/code/posthog"); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBe("/Users/me/code/posthog"); + }); + + it("derives remote workspace path from task.repository when cwd is missing", () => { + mockUseTasks.mockReturnValue({ + data: [{ id: "task-1", repository: "posthog/posthog.com" }], + }); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBe("/tmp/workspace/repos/posthog/posthog.com"); + }); + + it("prefers local cwd over derived remote path when both could resolve", () => { + mockUseCwd.mockReturnValue("/Users/me/code/posthog"); + mockUseTasks.mockReturnValue({ + data: [{ id: "task-1", repository: "posthog/posthog.com" }], + }); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBe("/Users/me/code/posthog"); + }); + + it("returns undefined when task has no repository", () => { + mockUseTasks.mockReturnValue({ data: [{ id: "task-1" }] }); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when repository is malformed", () => { + mockUseTasks.mockReturnValue({ + data: [{ id: "task-1", repository: "not-a-valid-repo-string" }], + }); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when task is not found", () => { + mockUseTasks.mockReturnValue({ + data: [{ id: "other-task", repository: "posthog/posthog.com" }], + }); + const { result } = renderHook(() => useDisplayRepoPath("task-1")); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when taskId is undefined", () => { + mockUseTasks.mockReturnValue({ + data: [{ id: "task-1", repository: "posthog/posthog.com" }], + }); + const { result } = renderHook(() => useDisplayRepoPath(undefined)); + expect(result.current).toBeUndefined(); + }); +}); diff --git a/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts new file mode 100644 index 000000000..f9f366852 --- /dev/null +++ b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts @@ -0,0 +1,21 @@ +import { useCwd } from "@features/sidebar/hooks/useCwd"; +import { useTasks } from "@features/tasks/hooks/useTasks"; +import { parseRepository } from "@utils/repository"; + +const REMOTE_WORKSPACE_PREFIX = "/tmp/workspace/repos"; + +export function useDisplayRepoPath( + taskId: string | undefined, +): string | undefined { + const localCwd = useCwd(taskId ?? ""); + const { data: tasks = [] } = useTasks(); + + if (localCwd) return localCwd; + if (!taskId) return undefined; + + const task = tasks.find((t) => t.id === taskId); + const parsed = task?.repository ? parseRepository(task.repository) : null; + if (!parsed) return undefined; + + return `${REMOTE_WORKSPACE_PREFIX}/${parsed.organization}/${parsed.repoName}`; +} From d1341a27c7307f8cc5116d2b5f4cbf4bf909e1e3 Mon Sep 17 00:00:00 2001 From: Tim Glaser Date: Mon, 18 May 2026 23:29:25 +0000 Subject: [PATCH 2/2] docs(sessions): explain why useDisplayRepoPath derives a remote path Generated-By: PostHog Code Task-Id: 1b29c9c1-769d-4c98-a3c1-04c703482115 --- .../renderer/features/sessions/hooks/useDisplayRepoPath.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts index f9f366852..6bc4a71cc 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useDisplayRepoPath.ts @@ -4,6 +4,12 @@ import { parseRepository } from "@utils/repository"; const REMOTE_WORKSPACE_PREFIX = "/tmp/workspace/repos"; +/** + * Returns the repo root to strip when displaying file paths in tool calls. + * Cloud tasks have no local cwd, so we derive the conventional sandbox + * clone location from `task.repository` — otherwise chips would render + * the full `/tmp/workspace/repos///...` sandbox path. + */ export function useDisplayRepoPath( taskId: string | undefined, ): string | undefined {