From 94365010498b7ddb6f90989af48ce9a14fd90faa Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sun, 16 Aug 2026 13:01:01 +0900 Subject: [PATCH] fix(codex): restart only stale app-servers --- src/codex/app-server-restart-service.ts | 16 ++++--- .../codex-app-server-restart-service.test.ts | 42 ++++++++++++++++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/codex/app-server-restart-service.ts b/src/codex/app-server-restart-service.ts index 50c272a1d1..9faa833a73 100644 --- a/src/codex/app-server-restart-service.ts +++ b/src/codex/app-server-restart-service.ts @@ -127,18 +127,22 @@ async function runCodexRestart(io: CodexRestartServiceIo): Promise [entry.pid, entry.startedAtMs] as const), + before.processes + .filter(entry => entry.startedAtMs !== null && entry.startedAtMs <= catalogMtimeMs) + .map(entry => [entry.pid, entry.startedAtMs] as const), ); const live = (io.listProcesses ?? listCodexAppServerProcesses)(io.processIo ?? {}); const candidates = live.filter(process => classifiedStarts.has(process.pid)); diff --git a/tests/codex-app-server-restart-service.test.ts b/tests/codex-app-server-restart-service.test.ts index c93978c21e..9c1e7cc210 100644 --- a/tests/codex-app-server-restart-service.test.ts +++ b/tests/codex-app-server-restart-service.test.ts @@ -95,6 +95,46 @@ describe("performCodexRestart", () => { expect(result.requested).toEqual([]); }); + test("a fresh classifier reading signals nothing", async () => { + let restarted = false; + const result = await performCodexRestart(baseIo({ + collectState: () => ({ + state: "fresh", + processes: [{ pid: 100, startedAtMs: 20 }], + catalogMtimeMs: 10, + }), + listProcesses: () => [proc(100)], + restart: () => { + restarted = true; + return { requested: [100], stopped: [100], surviving: [], failed: [] }; + }, + })); + + expect(restarted).toBe(false); + expect(result.code).toBe("nothing_running"); + expect(result.stateBefore).toBe("fresh"); + expect(result.requested).toEqual([]); + }); + + test("a mixed stale and fresh reading signals only stale app-servers", async () => { + let received: CodexAppServerProcess[] = []; + await performCodexRestart(baseIo({ + collectState: () => ({ + state: "stale", + processes: [{ pid: 100, startedAtMs: 5 }, { pid: 200, startedAtMs: 20 }], + catalogMtimeMs: 10, + }), + listProcesses: () => [proc(100), proc(200)], + readStartMs: () => new Map([[100, 5], [200, 20]]), + restart: targets => { + received = [...targets]; + return { requested: [100], stopped: [100], surviving: [], failed: [] }; + }, + })); + + expect(received.map(entry => entry.pid)).toEqual([100]); + }); + test("a survivor makes the result partially_stopped and unsuccessful", async () => { const result = await performCodexRestart(baseIo({ collectState: () => ({ @@ -556,4 +596,4 @@ describe("last-moment identity gate (through the real restart helper)", () => { expect(serialized).not.toContain("CodexAppServerIdentityChanged"); expect(result.failed).toEqual([4242]); }); -}); \ No newline at end of file +});