diff --git a/src/server/index.ts b/src/server/index.ts index 6c7e53f062..ee837cd986 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -602,10 +602,10 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server | null = null; let startupOwnershipStatePaths: readonly string[] | null = null; - // #2923: both synchronous startup ownership decisions keep their fresh, - // race-sensitive targeted task query. Only the expensive fallback listing is - // shared, and only while that targeted result stays byte-for-byte unchanged. - // Runtime ownership retries below intentionally omit this startup-local memo. + // #2923: retain a successful fallback listing only within the first startup + // ownership decision. A targeted query's bytes are not a Task Scheduler state + // generation, so the later race-sensitive decision must take a fresh listing. + // Runtime ownership retries below intentionally omit this startup-local memo too. const startupWindowsTaskListingCache = createWindowsTaskListingCache(); try { const homes = resolveServiceHomes(); @@ -797,7 +797,6 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server { expect(result.kind).toBe("unknown"); }); - test("one startup keeps two targeted queries but shares one unchanged full listing (#2923)", async () => { + test("a startup refreshes the full listing when a task appears after the second targeted snapshot", async () => { const codexHome = join(home, "codex"); mkdirSync(codexHome, { recursive: true }); process.env.CODEX_HOME = codexHome; @@ -227,15 +227,22 @@ describe("Windows ownership probe hardening regressions", () => { let targetedQueries = 0; let fullListings = 0; + let taskRegistered = false; const runRaw: RawProbeRunner = (file, args) => { if (!file.toLowerCase().endsWith("schtasks.exe")) return raw(1, "", "unexpected executable"); if (args.includes("/xml")) { targetedQueries += 1; + // Model a localized targeted query that took its absent snapshot before + // a concurrent installer committed the task, then returned unchanged + // opaque bytes. Only the following fresh listing can observe the task. + if (targetedQueries === 2) taskRegistered = true; return { status: 1, stdout: Buffer.alloc(0), stderr: GBK_TASK_NOT_FOUND, timedOut: false, spawnFailed: false }; } if (args.includes("/fo")) { fullListings += 1; - return raw(0, '"\\SomeOtherTask","N/A","Ready"\r\n'); + return raw(0, taskRegistered + ? '"\\opencodex-proxy","N/A","Ready"\r\n' + : '"\\SomeOtherTask","N/A","Ready"\r\n'); } return raw(1, "", "unexpected query"); }; @@ -261,9 +268,9 @@ describe("Windows ownership probe hardening regressions", () => { }, }); try { - expect(ownerships.slice(0, 2)).toEqual(["owned", "owned"]); + expect(ownerships.slice(0, 2)).toEqual(["owned", "unknown"]); expect(targetedQueries).toBe(2); - expect(fullListings).toBe(1); + expect(fullListings).toBe(2); } finally { await server.stop(true); }