From 6ddd3973253e9402e0f109f34ff91afd632de597 Mon Sep 17 00:00:00 2001 From: maoxin1234 <875408344@qq.com> Date: Sun, 13 Sep 2026 13:22:53 +0800 Subject: [PATCH 1/2] fix(manifest): normalize Windows extended-length and device path prefixes (#4442) --- src/codex/history-manifest.ts | 14 ++++++++++++- .../codex-history-provider.test.ts | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/codex/history-manifest.ts b/src/codex/history-manifest.ts index 01e649b306..e458cbac8d 100644 --- a/src/codex/history-manifest.ts +++ b/src/codex/history-manifest.ts @@ -54,8 +54,20 @@ export type CodexHistoryManifestValidation = readonly scope: "manifest" | "entry-shape" | "entry-provenance"; }; +function stripWindowsPathPrefix(path: string): string { + if (process.platform !== "win32") return path; + if (/^(\\\\(\?|\.)\\UNC\\|\/\/(\?|\.)\/UNC\/)/i.test(path)) { + return "\\\\" + path.slice(8); + } + if (/^(\\\\(\?|\.)\\|\/\/(\?|\.)\/)/.test(path)) { + return path.slice(4); + } + return path; +} + function codexHistoryPathIdentity(path: string): string { - const canonical = resolve(path); + const stripped = stripWindowsPathPrefix(path); + const canonical = resolve(stripped); return process.platform === "win32" ? canonical.toLowerCase() : canonical; } diff --git a/tests/codex-integration/codex-history-provider.test.ts b/tests/codex-integration/codex-history-provider.test.ts index 972245846f..007002e018 100644 --- a/tests/codex-integration/codex-history-provider.test.ts +++ b/tests/codex-integration/codex-history-provider.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { Database } from "bun:sqlite"; import { afterEach, describe, expect, setDefaultTimeout, test } from "bun:test"; import { classifyRecoverableHistoryError, countPendingOpencodexHistory, historyBackupPathFor, isRecoverableHistoryError, migrateHistoryToOpenai, restoreLegacyOpenaiHistory, restoredUserEventFor, setAfterNoopPendingCountForTests, setAfterStrictHistoryRolloutAppendForTests, setBeforeHistoryApplyTransactionForTests, setBeforeHistoryBackupConsumeForTests, setBeforeStrictHistoryRolloutAppendForTests, setHistoryDbBusyTimeoutForTests, snapshotCodexHistoryNoop, syncCodexHistoryProvider, withHistoryRetry } from "../../src/codex/history-provider"; +import { sameCodexHistoryPath } from "../../src/codex/history-manifest"; import { INVALID_HISTORY_BACKUP_FIXTURES, validHistoryBackupFixture } from "../helpers/codex-history-manifest-fixtures"; import { preflightCodexHistoryInjection, setHistoryAppendHooksForTests } from "../../src/codex/history-provider"; @@ -1513,4 +1514,23 @@ describe("Design B migration helpers", () => { expect(db.query("SELECT model_provider FROM threads WHERE id = 'thread-1'").get()).toEqual({ model_provider: "openai" }); db.close(); }); + + test("sameCodexHistoryPath normalizes Windows extended-length and device path prefixes", () => { + if (process.platform !== "win32") return; + const std = "C:\\Users\\test\\state.db"; + const ext = "\\\\?\\C:\\Users\\test\\state.db"; + const dev = "\\\\.\\C:\\Users\\test\\state.db"; + const fwd = "//?/C:/Users/test/state.db"; + expect(sameCodexHistoryPath(std, ext)).toBe(true); + expect(sameCodexHistoryPath(ext, std)).toBe(true); + expect(sameCodexHistoryPath(std, dev)).toBe(true); + expect(sameCodexHistoryPath(std, fwd)).toBe(true); + }); + + test("sameCodexHistoryPath normalizes Windows UNC paths with extended prefix", () => { + if (process.platform !== "win32") return; + const unc = "\\\\server\\share\\rollout.jsonl"; + const extUnc = "\\\\?\\UNC\\server\\share\\rollout.jsonl"; + expect(sameCodexHistoryPath(unc, extUnc)).toBe(true); + }); }); From 02509da67127170f2faef3b7880f238a075202e5 Mon Sep 17 00:00:00 2001 From: maoxin1234 <875408344@qq.com> Date: Sun, 13 Sep 2026 15:27:48 +0800 Subject: [PATCH 2/2] fix(manifest): export stripWindowsPathPrefix with cross-platform tests --- src/codex/history-manifest.ts | 7 +++++-- .../codex-history-provider.test.ts | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/codex/history-manifest.ts b/src/codex/history-manifest.ts index e458cbac8d..047dd036c4 100644 --- a/src/codex/history-manifest.ts +++ b/src/codex/history-manifest.ts @@ -54,8 +54,11 @@ export type CodexHistoryManifestValidation = readonly scope: "manifest" | "entry-shape" | "entry-provenance"; }; -function stripWindowsPathPrefix(path: string): string { - if (process.platform !== "win32") return path; +/** + * Strips Windows extended-length and device prefixes (e.g. \\?\, \\.\, //?/, //./, \\?\UNC\, //?/UNC/) + * to allow deterministic path comparison across tools. Pure string operation independent of host OS. + */ +export function stripWindowsPathPrefix(path: string): string { if (/^(\\\\(\?|\.)\\UNC\\|\/\/(\?|\.)\/UNC\/)/i.test(path)) { return "\\\\" + path.slice(8); } diff --git a/tests/codex-integration/codex-history-provider.test.ts b/tests/codex-integration/codex-history-provider.test.ts index 007002e018..53f298bac3 100644 --- a/tests/codex-integration/codex-history-provider.test.ts +++ b/tests/codex-integration/codex-history-provider.test.ts @@ -4,7 +4,7 @@ import { join } from "node:path"; import { Database } from "bun:sqlite"; import { afterEach, describe, expect, setDefaultTimeout, test } from "bun:test"; import { classifyRecoverableHistoryError, countPendingOpencodexHistory, historyBackupPathFor, isRecoverableHistoryError, migrateHistoryToOpenai, restoreLegacyOpenaiHistory, restoredUserEventFor, setAfterNoopPendingCountForTests, setAfterStrictHistoryRolloutAppendForTests, setBeforeHistoryApplyTransactionForTests, setBeforeHistoryBackupConsumeForTests, setBeforeStrictHistoryRolloutAppendForTests, setHistoryDbBusyTimeoutForTests, snapshotCodexHistoryNoop, syncCodexHistoryProvider, withHistoryRetry } from "../../src/codex/history-provider"; -import { sameCodexHistoryPath } from "../../src/codex/history-manifest"; +import { sameCodexHistoryPath, stripWindowsPathPrefix } from "../../src/codex/history-manifest"; import { INVALID_HISTORY_BACKUP_FIXTURES, validHistoryBackupFixture } from "../helpers/codex-history-manifest-fixtures"; import { preflightCodexHistoryInjection, setHistoryAppendHooksForTests } from "../../src/codex/history-provider"; @@ -1515,6 +1515,19 @@ describe("Design B migration helpers", () => { db.close(); }); + test("stripWindowsPathPrefix normalizes Windows extended prefixes platform-independently", () => { + expect(stripWindowsPathPrefix("\\\\?\\C:\\Users\\test\\state.db")).toBe("C:\\Users\\test\\state.db"); + expect(stripWindowsPathPrefix("\\\\.\\C:\\Users\\test\\state.db")).toBe("C:\\Users\\test\\state.db"); + expect(stripWindowsPathPrefix("//?/C:/Users/test/state.db")).toBe("C:/Users/test/state.db"); + expect(stripWindowsPathPrefix("//./C:/Users/test/state.db")).toBe("C:/Users/test/state.db"); + expect(stripWindowsPathPrefix("\\\\?\\UNC\\server\\share\\path")).toBe("\\\\server\\share\\path"); + expect(stripWindowsPathPrefix("\\\\?\\unc\\server\\share\\path")).toBe("\\\\server\\share\\path"); + expect(stripWindowsPathPrefix("//?/UNC/server/share/path")).toBe("\\\\server/share/path"); + expect(stripWindowsPathPrefix("C:\\standard\\path")).toBe("C:\\standard\\path"); + expect(stripWindowsPathPrefix("/unix/style/path")).toBe("/unix/style/path"); + expect(stripWindowsPathPrefix("")).toBe(""); + }); + test("sameCodexHistoryPath normalizes Windows extended-length and device path prefixes", () => { if (process.platform !== "win32") return; const std = "C:\\Users\\test\\state.db";