From 64f9a7071200b0bcd01ae4c60c218ef052cf9e5f Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 11 May 2026 08:54:46 +0900 Subject: [PATCH] fix(config): use r+ when fsyncing migrated temp file Apply the Windows-safe open mode in migrateLegacyPluginEntry and add a regression test to assert the temp fd is opened with r+ before fsync. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/shared/migrate-legacy-plugin-entry.ts | 2 +- .../migrate-legacy-plugin-entry.test.ts | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/shared/migrate-legacy-plugin-entry.ts b/src/shared/migrate-legacy-plugin-entry.ts index 80a015c6e..0eeb1949d 100644 --- a/src/shared/migrate-legacy-plugin-entry.ts +++ b/src/shared/migrate-legacy-plugin-entry.ts @@ -54,7 +54,7 @@ export function migrateLegacyPluginEntry(configPath: string): boolean { const tempPath = `${configPath}.tmp` writeFileSync(tempPath, updated, "utf-8") - const tempFileDescriptor = openSync(tempPath, "r") + const tempFileDescriptor = openSync(tempPath, "r+") try { fsyncSync(tempFileDescriptor) } finally { diff --git a/src/shared/zauc-mocks-migrate-legacy-plugin/migrate-legacy-plugin-entry.test.ts b/src/shared/zauc-mocks-migrate-legacy-plugin/migrate-legacy-plugin-entry.test.ts index 993e356ab..6cd489062 100644 --- a/src/shared/zauc-mocks-migrate-legacy-plugin/migrate-legacy-plugin-entry.test.ts +++ b/src/shared/zauc-mocks-migrate-legacy-plugin/migrate-legacy-plugin-entry.test.ts @@ -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[0], flags: Parameters[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("#when migrating the config", () => { it("#then preserves the version pin", async () => { @@ -217,4 +252,4 @@ describe("migrateLegacyPluginEntry", () => { }) }) }) -}) \ No newline at end of file +})