Merge pull request #3910 from code-yeongyu/fix/config-migration-repeated-bak

fix(config): use r+ mode for fsync on Windows to prevent migration retry loop
This commit is contained in:
YeonGyu-Kim
2026-05-11 09:05:12 +09:00
committed by GitHub
3 changed files with 38 additions and 3 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ export function migrateLegacyPluginEntry(configPath: string): boolean {
const tempPath = `${configPath}.tmp` const tempPath = `${configPath}.tmp`
writeFileSync(tempPath, updated, "utf-8") writeFileSync(tempPath, updated, "utf-8")
const tempFileDescriptor = openSync(tempPath, "r") const tempFileDescriptor = openSync(tempPath, "r+")
try { try {
fsyncSync(tempFileDescriptor) fsyncSync(tempFileDescriptor)
} finally { } finally {
+1 -1
View File
@@ -16,7 +16,7 @@ export function writeFileAtomically(
): void { ): void {
const tempPath = `${filePath}.tmp` const tempPath = `${filePath}.tmp`
writeFileSync(tempPath, content, "utf-8") writeFileSync(tempPath, content, "utf-8")
const tempFileDescriptor = openSync(tempPath, "r") const tempFileDescriptor = openSync(tempPath, "r+")
try { try {
tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync) tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync)
} finally { } finally {
@@ -96,6 +96,41 @@ describe("migrateLegacyPluginEntry", () => {
}) })
}) })
describe("#given migration writes a temp file for fsync", () => {
describe("#when opening the temp file descriptor", () => {
it("#then uses r+ mode to satisfy FlushFileBuffers requirements on Windows", async () => {
const configPath = join(testDir, "opencode.json")
writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode@latest"] }, null, 2))
const fs = await import("node:fs")
const originalOpenSync = fs.openSync
const openSyncCalls: string[] = []
mock.module("node:fs", () => ({
...fs,
openSync: (path: Parameters<typeof fs.openSync>[0], flags: Parameters<typeof fs.openSync>[1]) => {
openSyncCalls.push(String(flags))
return originalOpenSync(path, flags)
},
}))
try {
const { migrateLegacyPluginEntry } = await importFreshMigrationModule()
const result = migrateLegacyPluginEntry(configPath)
expect(result).toBe(true)
expect(openSyncCalls).toContain("r+")
} finally {
mock.module("node:fs", () => ({
...fs,
openSync: originalOpenSync,
}))
}
})
})
})
describe("#given opencode.json contains pinned oh-my-opencode version", () => { describe("#given opencode.json contains pinned oh-my-opencode version", () => {
describe("#when migrating the config", () => { describe("#when migrating the config", () => {
it("#then preserves the version pin", async () => { it("#then preserves the version pin", async () => {
@@ -217,4 +252,4 @@ describe("migrateLegacyPluginEntry", () => {
}) })
}) })
}) })
}) })