diff --git a/codex-cli/src/cli.tsx b/codex-cli/src/cli.tsx index 4ddd7d4914..adfc96145b 100644 --- a/codex-cli/src/cli.tsx +++ b/codex-cli/src/cli.tsx @@ -168,6 +168,7 @@ const cli = meow( disableResponseStorage: { type: "boolean", + default: undefined, description: "Disable server-side response storage (sends full conversation context with every request)", }, @@ -297,7 +298,7 @@ config = { disableResponseStorage: cli.flags.disableResponseStorage !== undefined ? Boolean(cli.flags.disableResponseStorage) - : config.disableResponseStorage, + : (config.disableResponseStorage ?? false), }; // Check for updates after loading config. This is important because we write state file in diff --git a/codex-cli/src/utils/config.ts b/codex-cli/src/utils/config.ts index 566e084d9b..fb9e1ad770 100644 --- a/codex-cli/src/utils/config.ts +++ b/codex-cli/src/utils/config.ts @@ -365,7 +365,7 @@ export const loadConfig = ( instructions: combinedInstructions, notify: storedConfig.notify === true, approvalMode: storedConfig.approvalMode, - disableResponseStorage: storedConfig.disableResponseStorage ?? false, + disableResponseStorage: storedConfig.disableResponseStorage === true, }; // ----------------------------------------------------------------------- diff --git a/codex-cli/tests/disableResponseStorage.agentLoop.test.ts b/codex-cli/tests/disableResponseStorage.agentLoop.test.ts new file mode 100644 index 0000000000..77dee42fff --- /dev/null +++ b/codex-cli/tests/disableResponseStorage.agentLoop.test.ts @@ -0,0 +1,79 @@ +/** + * codex-cli/tests/disableResponseStorage.agentLoop.test.ts + * + * Verifies AgentLoop's request-building logic for both values of + * disableResponseStorage. + */ + +import { describe, it, expect, vi } from "vitest"; +import { AgentLoop } from "../src/utils/agent/agent-loop"; +import type { AppConfig } from "../src/utils/config"; + +/* ─────────── 1. Spy + module mock ──────────────────────────────── */ +const createSpy = vi.fn().mockResolvedValue({ + data: { id: "resp_123", status: "completed", output: [] }, +}); + +vi.mock("openai", () => ({ + default: class { + public responses = { create: createSpy }; + }, + APIConnectionTimeoutError: class extends Error {}, +})); + +/* ─────────── 2. Parametrised tests ─────────────────────────────── */ +describe.each([ + { flag: true, title: "omits previous_response_id & sets store:false" }, + { flag: false, title: "sends previous_response_id & allows store:true" }, +])("AgentLoop with disableResponseStorage=%s", ({ flag, title }) => { + /* build a fresh config for each case */ + const cfg: AppConfig = { + model: "o4-mini", + provider: "openai", + instructions: "", + disableResponseStorage: flag, + notify: false, + }; + + it(title, async () => { + /* reset spy per iteration */ + createSpy.mockClear(); + + const loop = new AgentLoop({ + model: cfg.model, + provider: cfg.provider, + config: cfg, + instructions: "", + approvalPolicy: "suggest", + disableResponseStorage: flag, + additionalWritableRoots: [], + onItem() {}, + onLoading() {}, + getCommandConfirmation: async () => ({ review: "approve" }), + onLastResponseId() {}, + }); + + await loop.run([{ type: "text", content: "hello" }]); + expect(createSpy).toHaveBeenCalledTimes(1); + + const payload = createSpy.mock.calls[0][0]; + + if (flag) { + /* behaviour when ZDR is *on* */ + expect(payload).not.toHaveProperty("previous_response_id"); + payload.input.forEach((m: any) => + expect(m.store === undefined ? false : m.store).toBe(false), + ); + } else { + /* behaviour when ZDR is *off* */ + expect(payload).toHaveProperty("previous_response_id"); + // first user message is usually non-stored; assistant messages will be stored + // so here we just assert the property is not forcibly set to false + payload.input.forEach((m: any) => { + if ("store" in m) { + expect(m.store).not.toBe(false); + } + }); + } + }); +}); diff --git a/codex-cli/tests/disableResponseStorage.test.ts b/codex-cli/tests/disableResponseStorage.test.ts new file mode 100644 index 0000000000..d3f632fdde --- /dev/null +++ b/codex-cli/tests/disableResponseStorage.test.ts @@ -0,0 +1,42 @@ +/** + * codex/codex-cli/tests/disableResponseStorage.test.ts + */ + +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +import { loadConfig, saveConfig } from "../src/utils/config"; + +const sandboxHome = mkdtempSync(join(tmpdir(), "codex-home-")); +const codexDir = join(sandboxHome, ".codex"); +const yamlPath = join(codexDir, "config.yaml"); + +describe("disableResponseStorage persistence", () => { + beforeAll(() => { + // mkdir -p ~/.codex inside the sandbox + rmSync(codexDir, { recursive: true, force: true }); + require("fs").mkdirSync(codexDir, { recursive: true }); + + // seed YAML with ZDR enabled + writeFileSync(yamlPath, "model: o4-mini\ndisableResponseStorage: true\n"); + }); + + afterAll(() => { + rmSync(sandboxHome, { recursive: true, force: true }); + }); + + it("keeps disableResponseStorage=true across load/save cycle", async () => { + // 1️⃣ explicitly load the sandbox file + const cfg1 = loadConfig(yamlPath); + expect(cfg1.disableResponseStorage).toBe(true); + + // 2️⃣ save right back to the same file + await saveConfig(cfg1, yamlPath); + + // 3️⃣ reload and re-assert + const cfg2 = loadConfig(yamlPath); + expect(cfg2.disableResponseStorage).toBe(true); + }); +});