From f950de95ba3744dad248f51aaaa893afb3a22f4e Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:49:07 -0800 Subject: [PATCH] fix: ensure projects that go from having no commits to having commits have sessions migrated (#5105) Co-authored-by: GitHub Action --- .github/workflows/review.yml | 4 +-- packages/opencode/src/project/project.ts | 31 ++++++++++++++++++++++++ packages/opencode/src/util/queue.ts | 13 ++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index c190bb44a..08fcc388d 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -48,7 +48,7 @@ jobs: OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "gh pr review*": "deny", "*": "deny" } }' run: | PR_BODY=$(jq -r .body pr_data.json) - opencode run -m anthropic/claude-sonnet-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}' + opencode run -m anthropic/claude-opus-4-5 "A new pull request has been created: '${{ steps.pr-details.outputs.title }}' ${{ steps.pr-number.outputs.number }} @@ -75,4 +75,4 @@ jobs: -f 'body=[summary of issue]' -f 'commit_id=${{ steps.pr-details.outputs.sha }}' -f 'path=[path-to-file]' -F \"line=[line]\" -f 'side=RIGHT' \`\`\` - Only create comments for actual violations. If the code follows all guidelines, comment 'lgtm' AND NOTHING ELSE!!!!." + Only create comments for actual violations. If the code follows all guidelines, comment on the issue using gh cli: 'lgtm' AND NOTHING ELSE!!!!." diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index 0bf50e16c..b3b724005 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -5,6 +5,8 @@ import { $ } from "bun" import { Storage } from "../storage/storage" import { Log } from "../util/log" import { Flag } from "@/flag/flag" +import { Session } from "../session" +import { work } from "../util/queue" export namespace Project { const log = Log.create({ service: "project" }) @@ -77,6 +79,10 @@ export namespace Project { .text() .then((x) => path.resolve(worktree, x.trim())) const projectID = id || "global" + const existing = id ? await Storage.read(["project", id]).catch(() => undefined) : undefined + if (!existing) { + await migrateFromGlobal(projectID, worktree) + } const project: Info = { id: projectID, worktree, @@ -90,6 +96,31 @@ export namespace Project { return project } + async function migrateFromGlobal(newProjectID: string, worktree: string) { + const globalProject = await Storage.read(["project", "global"]).catch(() => undefined) + if (!globalProject) return + + const globalSessions = await Storage.list(["session", "global"]).catch(() => []) + if (globalSessions.length === 0) return + + log.info("migrating sessions from global", { newProjectID, worktree, count: globalSessions.length }) + const worktreePrefix = worktree.endsWith(path.sep) ? worktree : worktree + path.sep + + await work(10, globalSessions, async (key) => { + const sessionID = key[key.length - 1] + const session = await Storage.read(key).catch(() => undefined) + if (!session) return + if (session.directory && session.directory !== worktree && !session.directory.startsWith(worktreePrefix)) return + + session.projectID = newProjectID + log.info("migrating session", { sessionID, from: "global", to: newProjectID }) + await Storage.write(["session", newProjectID, sessionID], session) + await Storage.remove(key) + }).catch((error) => { + log.error("failed to migrate sessions from global to project", { error, projectId: newProjectID }) + }) + } + export async function setInitialized(projectID: string) { await Storage.update(["project", projectID], (draft) => { draft.time.initialized = Date.now() diff --git a/packages/opencode/src/util/queue.ts b/packages/opencode/src/util/queue.ts index 259d785ce..a1af53fe8 100644 --- a/packages/opencode/src/util/queue.ts +++ b/packages/opencode/src/util/queue.ts @@ -17,3 +17,16 @@ export class AsyncQueue implements AsyncIterable { while (true) yield await this.next() } } + +export async function work(concurrency: number, items: T[], fn: (item: T) => Promise) { + const pending = [...items] + await Promise.all( + Array.from({ length: concurrency }, async () => { + while (true) { + const item = pending.pop() + if (item === undefined) return + await fn(item) + } + }), + ) +}