Skip to content

Commit 4b9b86b

Browse files
fix(opencode): lost sessions across worktrees and orphan branches (anomalyco#16389)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
1 parent f54abe5 commit 4b9b86b

2 files changed

Lines changed: 50 additions & 3 deletions

File tree

packages/opencode/src/project/project.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export namespace Project {
147147

148148
// generate id from root commit
149149
if (!id) {
150-
const roots = await git(["rev-list", "--max-parents=0", "--all"], {
150+
const roots = await git(["rev-list", "--max-parents=0", "HEAD"], {
151151
cwd: sandbox,
152152
})
153153
.then(async (result) =>
@@ -170,7 +170,8 @@ export namespace Project {
170170

171171
id = roots[0] ? ProjectID.make(roots[0]) : undefined
172172
if (id) {
173-
await Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined)
173+
// Write to common dir so the cache is shared across worktrees.
174+
await Filesystem.write(path.join(worktree, ".git", "opencode"), id).catch(() => undefined)
174175
}
175176
}
176177

packages/opencode/test/project/project.test.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mock.module("../../src/util/git", () => ({
2323
mode === "rev-list-fail" &&
2424
cmd.includes("git rev-list") &&
2525
cmd.includes("--max-parents=0") &&
26-
cmd.includes("--all")
26+
cmd.includes("HEAD")
2727
) {
2828
return Promise.resolve({
2929
exitCode: 128,
@@ -172,6 +172,52 @@ describe("Project.fromDirectory with worktrees", () => {
172172
}
173173
})
174174

175+
test("worktree should share project ID with main repo", async () => {
176+
const p = await loadProject()
177+
await using tmp = await tmpdir({ git: true })
178+
179+
const { project: main } = await p.fromDirectory(tmp.path)
180+
181+
const worktreePath = path.join(tmp.path, "..", path.basename(tmp.path) + "-wt-shared")
182+
try {
183+
await $`git worktree add ${worktreePath} -b shared-${Date.now()}`.cwd(tmp.path).quiet()
184+
185+
const { project: wt } = await p.fromDirectory(worktreePath)
186+
187+
expect(wt.id).toBe(main.id)
188+
189+
// Cache should live in the common .git dir, not the worktree's .git file
190+
const cache = path.join(tmp.path, ".git", "opencode")
191+
const exists = await Filesystem.exists(cache)
192+
expect(exists).toBe(true)
193+
} finally {
194+
await $`git worktree remove ${worktreePath}`
195+
.cwd(tmp.path)
196+
.quiet()
197+
.catch(() => {})
198+
}
199+
})
200+
201+
test("separate clones of the same repo should share project ID", async () => {
202+
const p = await loadProject()
203+
await using tmp = await tmpdir({ git: true })
204+
205+
// Create a bare remote, push, then clone into a second directory
206+
const bare = tmp.path + "-bare"
207+
const clone = tmp.path + "-clone"
208+
try {
209+
await $`git clone --bare ${tmp.path} ${bare}`.quiet()
210+
await $`git clone ${bare} ${clone}`.quiet()
211+
212+
const { project: a } = await p.fromDirectory(tmp.path)
213+
const { project: b } = await p.fromDirectory(clone)
214+
215+
expect(b.id).toBe(a.id)
216+
} finally {
217+
await $`rm -rf ${bare} ${clone}`.quiet().nothrow()
218+
}
219+
})
220+
175221
test("should accumulate multiple worktrees in sandboxes", async () => {
176222
const p = await loadProject()
177223
await using tmp = await tmpdir({ git: true })

0 commit comments

Comments
 (0)