From 23b133d702d9140d999b4c379b39854bcc21ed11 Mon Sep 17 00:00:00 2001 From: JUN <243035832+lidge-jun@users.noreply.github.com> Date: Sat, 7 Nov 2026 20:48:49 +0900 Subject: [PATCH 1/2] test: isolate codex update fixture process scans --- .../codex-cli-update-apply.test.ts | 85 ++++++++++++++++++- tests/helpers/codex-cli-apply-fixture.ts | 4 + .../helpers/codex-cli-process-scan-preload.ts | 40 +++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 tests/helpers/codex-cli-process-scan-preload.ts diff --git a/tests/codex-integration/codex-cli-update-apply.test.ts b/tests/codex-integration/codex-cli-update-apply.test.ts index dea2f9b1fb9..e9ffd20f440 100644 --- a/tests/codex-integration/codex-cli-update-apply.test.ts +++ b/tests/codex-integration/codex-cli-update-apply.test.ts @@ -14,6 +14,7 @@ import { fixtureArtifactSet } from "../helpers/codex-cli-apply-artifact"; import { repoPath, repoRoot } from "../helpers/repo-root"; import { removeTreeWithRetry } from "../helpers/remove-tree"; import { codexCliPlatformBacking } from "../../src/codex/cli-install-provenance"; +import { scanCodexSessionProcessesAsync } from "../../src/codex/app-server-processes"; import { observeCodexCliFileSet, sameCodexCliFiles } from "../../src/codex/cli-update-files"; import { createCodexCliUpdateFence, cancelCodexCliUpdateFence, codexCliUpdateIsCancelled, @@ -41,11 +42,11 @@ async function approved(f: ReturnType, deps = depsFor(f)) { expect(plan.applicable, JSON.stringify(plan)).toBe(true); expect(plan.planId).toMatch(/^[0-9a-f]{32}$/); return plan.planId!; } -function cli(f: ReturnType, args: string[]) { +function cli(f: ReturnType, args: string[], exitCode = 0) { const result = spawnSync(Bun.which("node")!, [repoPath("bin", "ocx.mjs"), "system", "codex-cli-update", ...args, "--json"], { - cwd: repoRoot(), encoding: "utf8", env: f.env, timeout: 60_000, + cwd: f.root, encoding: "utf8", env: f.env, timeout: 60_000, }); - expect(result.error).toBeUndefined(); expect(result.status).toBe(0); + expect(result.error).toBeUndefined(); expect(result.status).toBe(exitCode); return JSON.parse(result.stdout); } function fixtureInWorkspaceScratch() { @@ -373,6 +374,14 @@ describe.skipIf(!posix)("Codex CLI private-prefix POSIX apply", () => { try { await waitForManagementServer(base, server.child, server.closed, server.output); session = await issueFixtureGuiSession(base); + for (const [scan, refusal] of [["active", "blocked_active_session"], ["unavailable", "blocked_process_state_unknown"]] as const) { + writeFileSync(join(f.root, "process-scan-state.json"), JSON.stringify({ scans: [scan] })); + const response = await fetch(`${base}/api/codex/cli-update/plan`, { headers: fixtureSessionHeaders(session) }); + expect(response.ok).toBe(true); + expect(await response.json()).toMatchObject({ applicable: false, refusal }); + expect(readFileSync(f.trace, "utf8")).toBe(""); + } + writeFileSync(join(f.root, "process-scan-state.json"), JSON.stringify({ scans: ["empty", "empty", "empty"] })); const planResponse = await fetch(`${base}/api/codex/cli-update/plan`, { headers: fixtureSessionHeaders(session) }); if (!planResponse.ok) throw new Error(`isolated production plan refused (${planResponse.status})`); const plan = await planResponse.json() as { schemaVersion?: number; applicable: boolean; refusal?: string | null; @@ -527,6 +536,76 @@ describe.skipIf(!posix)("Codex CLI private-prefix POSIX apply", () => { } }, 120_000); + test.each([ + ["active", JSON.stringify({ scans: ["active"] }), "blocked_active_session"], + ["unavailable", JSON.stringify({ scans: ["unavailable"] }), "blocked_process_state_unknown"], + ["malformed", "not-json", "blocked_process_state_unknown"], + ["exhausted", JSON.stringify({ scans: [] }), "blocked_process_state_unknown"], + ])("fixture scan %s keeps the real plan refusal", (_name, state, refusal) => { + const f = fixture(); + writeFileSync(join(f.root, "process-scan-state.json"), state); + const plan = cli(f, ["plan"]); + expect(plan).toMatchObject({ applicable: false, refusal }); + expect(readFileSync(f.trace, "utf8")).toBe(""); + }, 120_000); + + test.each([["active", "blocked_active_session"], ["unavailable", "blocked_process_state_unknown"]] as const)( + "fixture final scan %s refuses before installer admission", (last, refusal) => { + const f = fixture(); + const plan = cli(f, ["plan"]); + expect(plan.applicable).toBe(true); + writeFileSync(join(f.root, "process-scan-state.json"), JSON.stringify({ scans: ["empty", "empty", last] })); + const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"], 1); + expect(result).toMatchObject({ outcome: "refused", reason: refusal }); + const scans = readFileSync(join(f.root, "process-scan-trace.jsonl"), "utf8").trim().split("\n") + .map(line => JSON.parse(line) as { value: string; index: number }); + expect(scans).toEqual([{ value: "empty", index: 1 }, { value: "empty", index: 1 }, + { value: "empty", index: 2 }, { value: last, index: 3 }]); + expect(readFileSync(f.trace, "utf8")).toBe(""); + }, 120_000); + + test("published launcher remains isolated from a real owned Codex-shaped process", async () => { + const f = fixture(); + const node = Bun.which("node"); + if (!node) throw new Error("fixture node executable unavailable"); + const entry = join(f.root, "codex"); + writeFileSync(entry, 'process.stdout.write("READY\\n"); process.stdin.resume();\n'); + const child = spawn(node, [entry], { cwd: f.root, env: f.env, stdio: ["pipe", "pipe", "pipe"] }); + const closed = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(resolve => { + child.once("close", (code, signal) => resolve({ code, signal })); + }); + try { + const ready = new Promise((resolve, reject) => { + let output = ""; + child.stdout!.on("data", chunk => { + output += String(chunk); + if (output.includes("\n")) resolve(output); + }); + child.once("error", reject); + closed.then(() => reject(new Error("owned fixture process exited before READY"))); + }); + expect(await ready).toBe("READY\n"); + const scan = await scanCodexSessionProcessesAsync(); + expect(scan.kind).toBe("observed"); + if (scan.kind !== "observed") throw new Error("native session scan unavailable"); + expect(scan.processes.some(process => process.pid === child.pid)).toBe(true); + const bunfig = join(f.root, "bunfig.toml"); + const fixtureConfig = readFileSync(bunfig, "utf8"); + writeFileSync(bunfig, ""); + expect(cli(f, ["plan"])).toMatchObject({ applicable: false, refusal: "blocked_active_session" }); + writeFileSync(bunfig, fixtureConfig); + const plan = cli(f, ["plan"]); + expect(plan.applicable).toBe(true); + const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"]); + expect(result).toMatchObject({ status: "applied", outcome: "applied", childState: "terminal" }); + expect(readFileSync(f.trace, "utf8").trim().split("\n")).toHaveLength(1); + } finally { + child.stdin?.end(); + const exit = await closed; + expect(exit).toEqual({ code: 0, signal: null }); + } + }, 120_000); + test("published CLI packs and applies through a symlinked TMPDIR using canonical staging", () => { const f = fixture(); const alias = join(f.root, "tmp-alias"); symlinkSync(f.temporaryRoot, alias, "dir"); diff --git a/tests/helpers/codex-cli-apply-fixture.ts b/tests/helpers/codex-cli-apply-fixture.ts index 73371d5d17c..9d5406afe66 100644 --- a/tests/helpers/codex-cli-apply-fixture.ts +++ b/tests/helpers/codex-cli-apply-fixture.ts @@ -4,6 +4,7 @@ import { dirname, join } from "node:path"; import { createNpmConfigIsolation, isolatedNpmTarget } from "../../src/codex/cli-update-plan"; import { fixtureArtifact, fixtureArtifactSet } from "./codex-cli-apply-artifact"; import type { CodexCliApplyResult } from "../../src/codex/cli-update-apply"; +import { helperPath } from "./repo-root"; /** Admission waits must also observe an early refusal/rejection. Otherwise a * diagnostic failure before install/fetch entry becomes a misleading timeout. @@ -100,6 +101,9 @@ export function createCodexCliApplyFixture(root: string, platform: NodeJS.Platfo ])); if (process.platform !== "win32") { writeFixtureNpmWrapper(fakeBin, node, realNpm, metadata, packs, trace); + // A private cwd makes this preload local to the published-launcher fixtures. + writeFileSync(join(root, "bunfig.toml"), `preload = [${JSON.stringify(helperPath("codex-cli-process-scan-preload.ts"))}]\n`); + writeFileSync(join(root, "process-scan-state.json"), JSON.stringify({ scans: ["empty", "empty", "empty"] })); } const launcher = process.platform === "win32" ? join(prefix, "codex.cmd") : join(prefix, "bin", "codex"); const env = { ...process.env, HOME: home, TMPDIR: temporaryRoot, OPENCODEX_HOME: join(home, "ocx"), diff --git a/tests/helpers/codex-cli-process-scan-preload.ts b/tests/helpers/codex-cli-process-scan-preload.ts new file mode 100644 index 00000000000..d3bbdaf1fd6 --- /dev/null +++ b/tests/helpers/codex-cli-process-scan-preload.ts @@ -0,0 +1,40 @@ +import { mock } from "bun:test"; +import { appendFileSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +// Only fixture-owned Bun processes load this file through their private bunfig. +// Keep the published CLI and update worker real; replace only their process view. +if (Bun.isMainThread) { + const workerModule = await import("../../src/lib/worker-embed"); + const spawnOriginal = workerModule.spawnWorker; + mock.module(import.meta.resolve("../../src/lib/worker-embed"), () => ({ + ...workerModule, + spawnWorker: (url: string, key: string) => key === "cli-update-worker" + ? new Worker(url, { preload: [fileURLToPath(import.meta.url)] }) + : spawnOriginal(url, key), + })); +} else { + const processModule = await import("../../src/codex/app-server-processes"); + const scanOriginal = processModule.scanCodexSessionProcesses; + const scanAsyncOriginal = processModule.scanCodexSessionProcessesAsync; + let index = 0; + const snapshots = () => { + const state = JSON.parse(readFileSync(join(process.cwd(), "process-scan-state.json"), "utf8")) as { scans?: unknown }; + if (!Array.isArray(state.scans) || !state.scans.every(value => ["empty", "active", "unavailable"].includes(value))) { + throw new Error("invalid fixture process scan state"); + } + const value = state.scans[index++]; + if (value === undefined) throw new Error("fixture process scan sequence exhausted"); + appendFileSync(join(process.cwd(), "process-scan-trace.jsonl"), JSON.stringify({ value, index }) + "\n"); + if (value === "unavailable") throw new Error("fixture process scan unavailable"); + return value === "active" ? [{ pid: 918273, commandLine: "codex", executable: "codex" }] : []; + }; + mock.module(import.meta.resolve("../../src/codex/app-server-processes"), () => ({ + ...processModule, + scanCodexSessionProcesses: (io: Parameters[0] = {}) => + scanOriginal({ ...io, listSnapshots: snapshots }), + scanCodexSessionProcessesAsync: (io: Parameters[0] = {}) => + scanAsyncOriginal({ ...io, listSnapshotsAsync: async () => snapshots() }), + })); +} From b6b09dddc785e1c4175a705cf01c74a987c16955 Mon Sep 17 00:00:00 2001 From: JUN <243035832+lidge-jun@users.noreply.github.com> Date: Sat, 7 Nov 2026 21:15:15 +0900 Subject: [PATCH 2/2] test: split update process fixture cases --- .../test-layout/layout-codex-collision.json | 1 + .../codex-cli-update-apply.test.ts | 75 +-------------- .../codex-cli-update-process-fixture.test.ts | 96 +++++++++++++++++++ tests/fixtures/test-layout-expected.json | 1 + 4 files changed, 100 insertions(+), 73 deletions(-) create mode 100644 tests/codex-integration/codex-cli-update-process-fixture.test.ts diff --git a/scripts/test-layout/layout-codex-collision.json b/scripts/test-layout/layout-codex-collision.json index d9af6341408..8bb668619f0 100644 --- a/scripts/test-layout/layout-codex-collision.json +++ b/scripts/test-layout/layout-codex-collision.json @@ -31,6 +31,7 @@ "codex-launcher-env.test.ts": "codex-integration", "codex-cli-update-artifacts.test.ts": "codex-integration", "codex-cli-update-apply.test.ts": "codex-integration", + "codex-cli-update-process-fixture.test.ts": "codex-integration", "codex-cli-update-paths.test.ts": "codex-integration", "codex-cli-update-admission.test.ts": "codex-integration", "codex-cli-update-recovery.test.ts": "codex-integration", diff --git a/tests/codex-integration/codex-cli-update-apply.test.ts b/tests/codex-integration/codex-cli-update-apply.test.ts index e9ffd20f440..687a8a47f3e 100644 --- a/tests/codex-integration/codex-cli-update-apply.test.ts +++ b/tests/codex-integration/codex-cli-update-apply.test.ts @@ -14,7 +14,6 @@ import { fixtureArtifactSet } from "../helpers/codex-cli-apply-artifact"; import { repoPath, repoRoot } from "../helpers/repo-root"; import { removeTreeWithRetry } from "../helpers/remove-tree"; import { codexCliPlatformBacking } from "../../src/codex/cli-install-provenance"; -import { scanCodexSessionProcessesAsync } from "../../src/codex/app-server-processes"; import { observeCodexCliFileSet, sameCodexCliFiles } from "../../src/codex/cli-update-files"; import { createCodexCliUpdateFence, cancelCodexCliUpdateFence, codexCliUpdateIsCancelled, @@ -42,11 +41,11 @@ async function approved(f: ReturnType, deps = depsFor(f)) { expect(plan.applicable, JSON.stringify(plan)).toBe(true); expect(plan.planId).toMatch(/^[0-9a-f]{32}$/); return plan.planId!; } -function cli(f: ReturnType, args: string[], exitCode = 0) { +function cli(f: ReturnType, args: string[]) { const result = spawnSync(Bun.which("node")!, [repoPath("bin", "ocx.mjs"), "system", "codex-cli-update", ...args, "--json"], { cwd: f.root, encoding: "utf8", env: f.env, timeout: 60_000, }); - expect(result.error).toBeUndefined(); expect(result.status).toBe(exitCode); + expect(result.error).toBeUndefined(); expect(result.status).toBe(0); return JSON.parse(result.stdout); } function fixtureInWorkspaceScratch() { @@ -536,76 +535,6 @@ describe.skipIf(!posix)("Codex CLI private-prefix POSIX apply", () => { } }, 120_000); - test.each([ - ["active", JSON.stringify({ scans: ["active"] }), "blocked_active_session"], - ["unavailable", JSON.stringify({ scans: ["unavailable"] }), "blocked_process_state_unknown"], - ["malformed", "not-json", "blocked_process_state_unknown"], - ["exhausted", JSON.stringify({ scans: [] }), "blocked_process_state_unknown"], - ])("fixture scan %s keeps the real plan refusal", (_name, state, refusal) => { - const f = fixture(); - writeFileSync(join(f.root, "process-scan-state.json"), state); - const plan = cli(f, ["plan"]); - expect(plan).toMatchObject({ applicable: false, refusal }); - expect(readFileSync(f.trace, "utf8")).toBe(""); - }, 120_000); - - test.each([["active", "blocked_active_session"], ["unavailable", "blocked_process_state_unknown"]] as const)( - "fixture final scan %s refuses before installer admission", (last, refusal) => { - const f = fixture(); - const plan = cli(f, ["plan"]); - expect(plan.applicable).toBe(true); - writeFileSync(join(f.root, "process-scan-state.json"), JSON.stringify({ scans: ["empty", "empty", last] })); - const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"], 1); - expect(result).toMatchObject({ outcome: "refused", reason: refusal }); - const scans = readFileSync(join(f.root, "process-scan-trace.jsonl"), "utf8").trim().split("\n") - .map(line => JSON.parse(line) as { value: string; index: number }); - expect(scans).toEqual([{ value: "empty", index: 1 }, { value: "empty", index: 1 }, - { value: "empty", index: 2 }, { value: last, index: 3 }]); - expect(readFileSync(f.trace, "utf8")).toBe(""); - }, 120_000); - - test("published launcher remains isolated from a real owned Codex-shaped process", async () => { - const f = fixture(); - const node = Bun.which("node"); - if (!node) throw new Error("fixture node executable unavailable"); - const entry = join(f.root, "codex"); - writeFileSync(entry, 'process.stdout.write("READY\\n"); process.stdin.resume();\n'); - const child = spawn(node, [entry], { cwd: f.root, env: f.env, stdio: ["pipe", "pipe", "pipe"] }); - const closed = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(resolve => { - child.once("close", (code, signal) => resolve({ code, signal })); - }); - try { - const ready = new Promise((resolve, reject) => { - let output = ""; - child.stdout!.on("data", chunk => { - output += String(chunk); - if (output.includes("\n")) resolve(output); - }); - child.once("error", reject); - closed.then(() => reject(new Error("owned fixture process exited before READY"))); - }); - expect(await ready).toBe("READY\n"); - const scan = await scanCodexSessionProcessesAsync(); - expect(scan.kind).toBe("observed"); - if (scan.kind !== "observed") throw new Error("native session scan unavailable"); - expect(scan.processes.some(process => process.pid === child.pid)).toBe(true); - const bunfig = join(f.root, "bunfig.toml"); - const fixtureConfig = readFileSync(bunfig, "utf8"); - writeFileSync(bunfig, ""); - expect(cli(f, ["plan"])).toMatchObject({ applicable: false, refusal: "blocked_active_session" }); - writeFileSync(bunfig, fixtureConfig); - const plan = cli(f, ["plan"]); - expect(plan.applicable).toBe(true); - const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"]); - expect(result).toMatchObject({ status: "applied", outcome: "applied", childState: "terminal" }); - expect(readFileSync(f.trace, "utf8").trim().split("\n")).toHaveLength(1); - } finally { - child.stdin?.end(); - const exit = await closed; - expect(exit).toEqual({ code: 0, signal: null }); - } - }, 120_000); - test("published CLI packs and applies through a symlinked TMPDIR using canonical staging", () => { const f = fixture(); const alias = join(f.root, "tmp-alias"); symlinkSync(f.temporaryRoot, alias, "dir"); diff --git a/tests/codex-integration/codex-cli-update-process-fixture.test.ts b/tests/codex-integration/codex-cli-update-process-fixture.test.ts new file mode 100644 index 00000000000..1e43f48deb1 --- /dev/null +++ b/tests/codex-integration/codex-cli-update-process-fixture.test.ts @@ -0,0 +1,96 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { spawn, spawnSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { scanCodexSessionProcessesAsync } from "../../src/codex/app-server-processes"; +import { createCodexCliApplyFixture } from "../helpers/codex-cli-apply-fixture"; +import { createCodexCliTestRoot } from "../helpers/codex-cli-fixture-root"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { repoPath } from "../helpers/repo-root"; + +const roots: string[] = []; +afterEach(() => { for (const root of roots.splice(0)) removeTreeWithRetry(root); }); +function fixture() { + const root = createCodexCliTestRoot("ocx-apply-private-"); roots.push(root); + return createCodexCliApplyFixture(root, process.platform, process.arch); +} +function cli(f: ReturnType, args: string[], exitCode = 0) { + const result = spawnSync(Bun.which("node")!, [repoPath("bin", "ocx.mjs"), "system", "codex-cli-update", ...args, "--json"], { + cwd: f.root, encoding: "utf8", env: f.env, timeout: 60_000, + }); + expect(result.error).toBeUndefined(); expect(result.status).toBe(exitCode); + return JSON.parse(result.stdout); +} + +describe.skipIf(process.platform === "win32")("Codex CLI private-prefix POSIX apply", () => { + test.each([ + ["active", JSON.stringify({ scans: ["active"] }), "blocked_active_session"], + ["unavailable", JSON.stringify({ scans: ["unavailable"] }), "blocked_process_state_unknown"], + ["malformed", "not-json", "blocked_process_state_unknown"], + ["exhausted", JSON.stringify({ scans: [] }), "blocked_process_state_unknown"], + ])("fixture scan %s keeps the real plan refusal", (_name, state, refusal) => { + const f = fixture(); + writeFileSync(join(f.root, "process-scan-state.json"), state); + const plan = cli(f, ["plan"]); + expect(plan).toMatchObject({ applicable: false, refusal }); + expect(readFileSync(f.trace, "utf8")).toBe(""); + }, 120_000); + + test.each([["active", "blocked_active_session"], ["unavailable", "blocked_process_state_unknown"]] as const)( + "fixture final scan %s refuses before installer admission", (last, refusal) => { + const f = fixture(); + const plan = cli(f, ["plan"]); + expect(plan.applicable).toBe(true); + writeFileSync(join(f.root, "process-scan-state.json"), JSON.stringify({ scans: ["empty", "empty", last] })); + const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"], 1); + expect(result).toMatchObject({ outcome: "refused", reason: refusal }); + const scans = readFileSync(join(f.root, "process-scan-trace.jsonl"), "utf8").trim().split("\n") + .map(line => JSON.parse(line) as { value: string; index: number }); + expect(scans).toEqual([{ value: "empty", index: 1 }, { value: "empty", index: 1 }, + { value: "empty", index: 2 }, { value: last, index: 3 }]); + expect(readFileSync(f.trace, "utf8")).toBe(""); + }, 120_000); + + test("published launcher remains isolated from a real owned Codex-shaped process", async () => { + const f = fixture(); + const node = Bun.which("node"); + if (!node) throw new Error("fixture node executable unavailable"); + const entry = join(f.root, "codex"); + writeFileSync(entry, 'process.stdout.write("READY\\n"); process.stdin.resume();\n'); + const child = spawn(node, [entry], { cwd: f.root, env: f.env, stdio: ["pipe", "pipe", "pipe"] }); + const closed = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(resolve => { + child.once("close", (code, signal) => resolve({ code, signal })); + }); + try { + const ready = new Promise((resolve, reject) => { + let output = ""; + child.stdout!.on("data", chunk => { + output += String(chunk); + if (output.includes("\n")) resolve(output); + }); + child.once("error", reject); + closed.then(() => reject(new Error("owned fixture process exited before READY"))); + }); + expect(await ready).toBe("READY\n"); + const scan = await scanCodexSessionProcessesAsync(); + expect(scan.kind).toBe("observed"); + if (scan.kind !== "observed") throw new Error("native session scan unavailable"); + expect(scan.processes.some(process => process.pid === child.pid)).toBe(true); + const bunfig = join(f.root, "bunfig.toml"); + const fixtureConfig = readFileSync(bunfig, "utf8"); + writeFileSync(bunfig, ""); + expect(cli(f, ["plan"])).toMatchObject({ applicable: false, refusal: "blocked_active_session" }); + writeFileSync(bunfig, fixtureConfig); + const plan = cli(f, ["plan"]); + expect(plan.applicable).toBe(true); + const result = cli(f, ["apply", "--plan-id", plan.planId, "--yes"]); + expect(result).toMatchObject({ status: "applied", outcome: "applied", childState: "terminal" }); + expect(readFileSync(f.trace, "utf8").trim().split("\n")).toHaveLength(1); + } finally { + child.stdin?.end(); + const exit = await closed; + expect(exit).toEqual({ code: 0, signal: null }); + } + }, 120_000); + +}); diff --git a/tests/fixtures/test-layout-expected.json b/tests/fixtures/test-layout-expected.json index 6c4a07b0bf2..0a83167547d 100644 --- a/tests/fixtures/test-layout-expected.json +++ b/tests/fixtures/test-layout-expected.json @@ -83,6 +83,7 @@ "codex-cli-windows-child.test.ts": "codex-integration", "codex-cli-update-artifacts.test.ts": "codex-integration", "codex-cli-update-apply.test.ts": "codex-integration", + "codex-cli-update-process-fixture.test.ts": "codex-integration", "codex-cli-update-paths.test.ts": "codex-integration", "codex-cli-update-admission.test.ts": "codex-integration", "codex-cli-update-recovery.test.ts": "codex-integration",