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 +});