summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Dwan <[email protected]>2026-03-13 13:51:55 -0600
committerGitHub <[email protected]>2026-03-13 15:51:55 -0400
commit4b9b86b54460e85b9a0dd1caf6311564a50faaaa (patch)
tree5601982a348850f40689d8275ebffa9081ca2d0d
parentf54abe58cfb5c35cbaaf8497f49435d2180fd0cb (diff)
downloadopencode-4b9b86b54460e85b9a0dd1caf6311564a50faaaa.tar.gz
opencode-4b9b86b54460e85b9a0dd1caf6311564a50faaaa.zip
fix(opencode): lost sessions across worktrees and orphan branches (#16389)
Co-authored-by: Aiden Cline <[email protected]>
-rw-r--r--packages/opencode/src/project/project.ts5
-rw-r--r--packages/opencode/test/project/project.test.ts48
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 })