Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,17 @@ export namespace Snapshot {
.quiet()
.cwd(Instance.directory)
.nothrow()
const tmpPackRemoved = await cleanupTmpPacks(git)
if (result.exitCode !== 0) {
log.warn("cleanup failed", {
exitCode: result.exitCode,
stderr: result.stderr.toString(),
stdout: result.stdout.toString(),
tmpPackRemoved,
})
return
}
log.info("cleanup", { prune })
log.info("cleanup", { prune, tmpPackRemoved })
}

export async function track() {
Expand All @@ -66,12 +68,34 @@ export namespace Snapshot {
await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
log.info("initialized")
}
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
const add = await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
if (add.exitCode !== 0) {
const tmpPackRemoved = await cleanupTmpPacks(git)
log.warn("snapshot add failed", {
exitCode: add.exitCode,
stderr: add.stderr.toString(),
stdout: add.stdout.toString(),
tmpPackRemoved,
})
return
Comment on lines +71 to +80
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanupTmpPacks(git) is triggered on git add / write-tree failures. If those failures are caused by a concurrent Snapshot.cleanup() running git gc (or another git maintenance operation) in the same repo, this could delete tmp_pack_* files while repack is still in progress and potentially corrupt the snapshot repository. Consider serializing snapshot git operations + cleanup via an in-process lock (similar to FileTime.withLock) or skipping tmp-pack deletion when a gc/repack lock is present (e.g., gc.pid / objects/pack/*.lock).

Copilot uses AI. Check for mistakes.
}

const write = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
.quiet()
.cwd(Instance.directory)
.nothrow()
.text()
if (write.exitCode !== 0) {
const tmpPackRemoved = await cleanupTmpPacks(git)
log.warn("snapshot write-tree failed", {
exitCode: write.exitCode,
stderr: write.stderr.toString(),
stdout: write.stdout.toString(),
tmpPackRemoved,
})
return
}

const hash = write.text()
log.info("tracking", { hash, cwd: Instance.directory, git })
return hash.trim()
}
Expand Down Expand Up @@ -253,4 +277,12 @@ export namespace Snapshot {
const project = Instance.project
return path.join(Global.Path.data, "snapshot", project.id)
}

async function cleanupTmpPacks(git: string) {
const pack = path.join(git, "objects", "pack")
const files = await fs.readdir(pack).catch(() => [])
const tmpPacks = files.filter((file) => file.startsWith("tmp_pack_"))
await Promise.all(tmpPacks.map((file) => fs.unlink(path.join(pack, file)).catch(() => undefined)))
return tmpPacks.length
}
}
Loading