diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 9870377b2169..6a9c87245083 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -232,9 +232,14 @@ const layer = Layer.effect( if (flags.experimentalIconDiscovery) yield* discover(existing).pipe(Effect.ignore, Effect.forkIn(scope)) + // Update worktree to the freshly resolved path when the stored path no + // longer exists on disk (project was moved or renamed). Otherwise keep + // the existing value so linked-worktree accumulation is preserved. + const staleWorktree = + projectID !== ProjectV2.ID.global && !(yield* fs.existsSafe(existing.worktree)) const result: Info = { ...existing, - worktree: projectID === ProjectV2.ID.global ? worktree : existing.worktree, + worktree: projectID === ProjectV2.ID.global || staleWorktree ? worktree : existing.worktree, vcs: data.vcs?.type ?? fakeVcs, time: { ...existing.time, updated: Date.now() }, } diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 804b92b08ec4..e2472b6892ba 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -396,6 +396,28 @@ describe("Project.fromDirectory with worktrees", () => { expect(result.project.sandboxes).not.toContain(tmp) }), ) + + it.live("updates worktree when project directory is moved", () => + Effect.gen(function* () { + const project = yield* Project.Service + const tmp = yield* tmpdirScoped({ git: true }) + + const original = yield* project.fromDirectory(tmp) + expect(original.project.worktree).toBe(tmp) + + const moved = tmp + "-moved" + yield* Effect.addFinalizer(() => + Effect.promise(() => $`rm -rf ${moved}`.quiet().nothrow()).pipe(Effect.ignore), + ) + yield* Effect.promise(() => $`mv ${tmp} ${moved}`.quiet()) + + const result = yield* project.fromDirectory(moved) + + expect(result.project.id).toBe(original.project.id) + expect(result.project.worktree).toBe(moved) + expect(result.project.sandboxes).not.toContain(tmp) + }), + ) }) describe("Project.discover", () => {