Skip to content

Commit 646f66c

Browse files
authored
Use stored worktree path when creating task terminals (#3074)
1 parent cfe5e53 commit 646f66c

2 files changed

Lines changed: 112 additions & 13 deletions

File tree

packages/workspace-server/src/services/shell/shell.test.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { describe, expect, it, vi } from "vitest";
1+
import { mkdtempSync, rmSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import path from "node:path";
4+
import { beforeEach, describe, expect, it, vi } from "vitest";
5+
import { createMockRepositoryRepository } from "../../db/repositories/repository-repository.mock";
6+
import { createMockWorkspaceRepository } from "../../db/repositories/workspace-repository.mock";
7+
import { createMockWorktreeRepository } from "../../db/repositories/worktree-repository.mock";
28
import { ShellEvent } from "./schemas";
39

410
const mockPty = vi.hoisted(() => ({
@@ -7,6 +13,13 @@ const mockPty = vi.hoisted(() => ({
713

814
vi.mock("node-pty", () => mockPty);
915

16+
const mockGitQueries = vi.hoisted(() => ({
17+
getCurrentBranch: vi.fn(async () => "feature-branch"),
18+
getDefaultBranch: vi.fn(async () => "main"),
19+
}));
20+
21+
vi.mock("@posthog/git/queries", () => mockGitQueries);
22+
1023
import { ShellService } from "./shell";
1124

1225
function createMockPtyProcess() {
@@ -21,7 +34,11 @@ function createMockPtyProcess() {
2134
};
2235
}
2336

24-
function createService() {
37+
function createService(overrides?: {
38+
repositoryRepo?: unknown;
39+
workspaceRepo?: unknown;
40+
worktreeRepo?: unknown;
41+
}) {
2542
const processTracking = {
2643
register: vi.fn(),
2744
unregister: vi.fn(),
@@ -37,9 +54,9 @@ function createService() {
3754
};
3855
const service = new ShellService(
3956
processTracking as never,
40-
{} as never,
41-
{} as never,
42-
{} as never,
57+
(overrides?.repositoryRepo ?? {}) as never,
58+
(overrides?.workspaceRepo ?? {}) as never,
59+
(overrides?.worktreeRepo ?? {}) as never,
4360
{ getWorktreeLocation: vi.fn(() => "/tmp/worktrees") } as never,
4461
logger as never,
4562
);
@@ -68,3 +85,72 @@ describe("ShellService.destroy", () => {
6885
expect(() => service.destroy("nonexistent")).not.toThrow();
6986
});
7087
});
88+
89+
describe("ShellService.createSession workspace env", () => {
90+
function createWorktreeTaskService(worktreePath: string) {
91+
const repositoryRepo = createMockRepositoryRepository();
92+
const workspaceRepo = createMockWorkspaceRepository();
93+
const worktreeRepo = createMockWorktreeRepository();
94+
const repo = repositoryRepo.create({ path: "/repos/code" });
95+
const workspace = workspaceRepo.create({
96+
taskId: "task-1",
97+
repositoryId: repo.id,
98+
mode: "worktree",
99+
});
100+
worktreeRepo.create({
101+
workspaceId: workspace.id,
102+
name: "spawn-tasks",
103+
path: worktreePath,
104+
});
105+
return createService({ repositoryRepo, workspaceRepo, worktreeRepo });
106+
}
107+
108+
function spawnedEnv(): Record<string, string | undefined> {
109+
return mockPty.spawn.mock.calls[0][2].env;
110+
}
111+
112+
beforeEach(() => {
113+
mockPty.spawn.mockReset();
114+
mockPty.spawn.mockReturnValue(createMockPtyProcess());
115+
mockGitQueries.getCurrentBranch.mockResolvedValue("feature-branch");
116+
mockGitQueries.getDefaultBranch.mockResolvedValue("main");
117+
});
118+
119+
it("uses the stored worktree path when it exists on disk", async () => {
120+
const tempDir = mkdtempSync(path.join(tmpdir(), "shell-test-"));
121+
try {
122+
const { service } = createWorktreeTaskService(tempDir);
123+
124+
await service.createSession({ sessionId: "session-1", taskId: "task-1" });
125+
126+
expect(spawnedEnv().POSTHOG_CODE_WORKSPACE_PATH).toBe(tempDir);
127+
expect(mockGitQueries.getCurrentBranch).toHaveBeenCalledWith(tempDir);
128+
} finally {
129+
rmSync(tempDir, { recursive: true, force: true });
130+
}
131+
});
132+
133+
it("falls back to the derived path when the stored path is missing", async () => {
134+
const { service } = createWorktreeTaskService("/does/not/exist");
135+
136+
await service.createSession({ sessionId: "session-1", taskId: "task-1" });
137+
138+
const derivedPath = path.join("/tmp/worktrees", "spawn-tasks", "code");
139+
expect(spawnedEnv().POSTHOG_CODE_WORKSPACE_PATH).toBe(derivedPath);
140+
expect(mockGitQueries.getCurrentBranch).toHaveBeenCalledWith(derivedPath);
141+
});
142+
143+
it("still creates the shell when env construction fails", async () => {
144+
mockGitQueries.getDefaultBranch.mockRejectedValue(
145+
new Error("Cannot use simple-git on a directory that does not exist"),
146+
);
147+
const { service } = createWorktreeTaskService("/does/not/exist");
148+
149+
await expect(
150+
service.createSession({ sessionId: "session-1", taskId: "task-1" }),
151+
).resolves.toBeDefined();
152+
153+
expect(mockPty.spawn).toHaveBeenCalledTimes(1);
154+
expect(spawnedEnv().POSTHOG_CODE_WORKSPACE_PATH).toBeUndefined();
155+
});
156+
});

packages/workspace-server/src/services/shell/shell.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -424,16 +424,29 @@ export class ShellService extends TypedEventEmitter<ShellEvents> {
424424
const worktree = this.worktreeRepo.findByWorkspaceId(workspace.id);
425425
if (worktree) {
426426
worktreeName = worktree.name;
427-
worktreePath = await this.deriveWorktreePath(repo.path, worktreeName);
427+
// The stored path is authoritative — reused worktrees can live
428+
// outside the managed worktree directory. Only derive when the
429+
// stored path is gone (e.g. stale row after a location move).
430+
worktreePath = existsSync(worktree.path)
431+
? worktree.path
432+
: this.deriveWorktreePath(repo.path, worktreeName);
428433
}
429434
}
430435

431-
return buildWorkspaceEnv({
432-
taskId,
433-
folderPath: repo.path,
434-
worktreePath,
435-
worktreeName,
436-
mode: workspace.mode,
437-
});
436+
try {
437+
return await buildWorkspaceEnv({
438+
taskId,
439+
folderPath: repo.path,
440+
worktreePath,
441+
worktreeName,
442+
mode: workspace.mode,
443+
});
444+
} catch (error) {
445+
this.log.warn(
446+
`Failed to build workspace env for task ${taskId}, starting shell without it`,
447+
error,
448+
);
449+
return undefined;
450+
}
438451
}
439452
}

0 commit comments

Comments
 (0)