From 4b9b86b54460e85b9a0dd1caf6311564a50faaaa Mon Sep 17 00:00:00 2001 From: Michael Dwan Date: Fri, 13 Mar 2026 13:51:55 -0600 Subject: [PATCH] fix(opencode): lost sessions across worktrees and orphan branches (#16389) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/opencode/src/project/project.ts | 5 +- .../opencode/test/project/project.test.ts | 48 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index d6f7d5a7e..1cef41c85 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -147,7 +147,7 @@ export namespace Project { // generate id from root commit if (!id) { - const roots = await git(["rev-list", "--max-parents=0", "--all"], { + const roots = await git(["rev-list", "--max-parents=0", "HEAD"], { cwd: sandbox, }) .then(async (result) => @@ -170,7 +170,8 @@ export namespace Project { id = roots[0] ? ProjectID.make(roots[0]) : undefined if (id) { - await Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined) + // Write to common dir so the cache is shared across worktrees. + await Filesystem.write(path.join(worktree, ".git", "opencode"), id).catch(() => undefined) } } diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index cc6b8dde5..a71fe0528 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -23,7 +23,7 @@ mock.module("../../src/util/git", () => ({ mode === "rev-list-fail" && cmd.includes("git rev-list") && cmd.includes("--max-parents=0") && - cmd.includes("--all") + cmd.includes("HEAD") ) { return Promise.resolve({ exitCode: 128, @@ -172,6 +172,52 @@ describe("Project.fromDirectory with worktrees", () => { } }) + test("worktree should share project ID with main repo", async () => { + const p = await loadProject() + await using tmp = await tmpdir({ git: true }) + + const { project: main } = await p.fromDirectory(tmp.path) + + const worktreePath = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt-shared") + try { + await $`git worktree add ${worktreePath} -b shared-${Date.now()}`.cwd(tmp.path).quiet() + + const { project: wt } = await p.fromDirectory(worktreePath) + + expect(wt.id).toBe(main.id) + + // Cache should live in the common .git dir, not the worktree's .git file + const cache = path.join(tmp.path, ".git", "opencode") + const exists = await Filesystem.exists(cache) + expect(exists).toBe(true) + } finally { + await $`git worktree remove ${worktreePath}` + .cwd(tmp.path) + .quiet() + .catch(() => {}) + } + }) + + test("separate clones of the same repo should share project ID", async () => { + const p = await loadProject() + await using tmp = await tmpdir({ git: true }) + + // Create a bare remote, push, then clone into a second directory + const bare = tmp.path + "-bare" + const clone = tmp.path + "-clone" + try { + await $`git clone --bare ${tmp.path} ${bare}`.quiet() + await $`git clone ${bare} ${clone}`.quiet() + + const { project: a } = await p.fromDirectory(tmp.path) + const { project: b } = await p.fromDirectory(clone) + + expect(b.id).toBe(a.id) + } finally { + await $`rm -rf ${bare} ${clone}`.quiet().nothrow() + } + }) + test("should accumulate multiple worktrees in sandboxes", async () => { const p = await loadProject() await using tmp = await tmpdir({ git: true })