From 965420cfc5b865b8d0494f0b2de05f8453c0ea9a Mon Sep 17 00:00:00 2001 From: John Gardner Date: Sat, 19 Apr 2025 10:25:25 -0400 Subject: [PATCH] feat: read approvalMode from config file (#298) This PR implements support for reading the approvalMode setting from the user's config file (`~/.codex/config.json` or `~/.codex/config.yaml`), allowing users to set a persistent default approval mode without needing to specify command-line flags for each session. Changes: - Added approvalMode to the AppConfig type in config.ts - Updated loadConfig() to read the approval mode from the config file - Modified saveConfig() to persist the approval mode setting - Updated CLI logic to respect the config-defined approval mode (while maintaining CLI flag priority) - Added comprehensive tests for approval mode config functionality - Updated README to document the new config option in both YAML and JSON formats - additions to `.gitignore` for other CLI tools Motivation: As a user who regularly works with CLI-tools, I found it odd to have to alias this with the command flags I wanted when `approvalMode` simply wasn't being parsed even though it was an optional prop in `config.ts`. This change allows me (and other users) to set the preference once in the config file, streamlining daily usage while maintaining the ability to override via command-line flags when needed. Testing: I've added a new test case loads and saves approvalMode correctly that verifies: - Reading the approvalMode from the config file works correctly - Saving the approvalMode to the config file works as expected - The value persists through load/save operations All tests related to the implementation are passing. --- .gitignore | 4 ++++ README.md | 13 ++++++++++- codex-cli/src/cli.tsx | 7 +++--- codex-cli/src/utils/config.ts | 3 +++ codex-cli/tests/config.test.tsx | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index be4139a3..d5f0dceb 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ result *.swp *~ +# cli tools +CLAUDE.md +.claude/ + # caches .cache/ .turbo/ diff --git a/README.md b/README.md index e6ab52ef..2094b939 100644 --- a/README.md +++ b/README.md @@ -281,11 +281,12 @@ pnpm link ## Configuration -Codex looks for config files in **`~/.codex/`**. +Codex looks for config files in **`~/.codex/`** (either YAML or JSON format). ```yaml # ~/.codex/config.yaml model: o4-mini # Default model +approvalMode: suggest # or auto-edit, full-auto fullAutoErrorMode: ask-user # or ignore-and-continue notify: true # Enable desktop notifications for responses safeCommands: @@ -293,6 +294,16 @@ safeCommands: - yarn lint # Automatically approve yarn lint ``` +```json +// ~/.codex/config.json +{ + "model": "o4-mini", + "approvalMode": "suggest", + "fullAutoErrorMode": "ask-user", + "notify": true +} +``` + You can also define custom instructions: ```yaml diff --git a/codex-cli/src/cli.tsx b/codex-cli/src/cli.tsx index 7c8e3d0d..169ead94 100644 --- a/codex-cli/src/cli.tsx +++ b/codex-cli/src/cli.tsx @@ -346,7 +346,7 @@ if (quietMode) { imagePaths: imagePaths || [], approvalPolicy: autoApproveEverything ? AutoApprovalMode.FULL_AUTO - : AutoApprovalMode.SUGGEST, + : config.approvalMode || AutoApprovalMode.SUGGEST, additionalWritableRoots, config, }); @@ -364,14 +364,15 @@ if (quietMode) { // it is more dangerous than --fullAuto we deliberately give it lower // priority so a user specifying both flags still gets the safer behaviour. // 3. --autoEdit – automatically approve edits, but prompt for commands. -// 4. Default – suggest mode (prompt for everything). +// 4. config.approvalMode - use the approvalMode setting from ~/.codex/config.json. +// 5. Default – suggest mode (prompt for everything). const approvalPolicy: ApprovalPolicy = cli.flags.fullAuto || cli.flags.approvalMode === "full-auto" ? AutoApprovalMode.FULL_AUTO : cli.flags.autoEdit || cli.flags.approvalMode === "auto-edit" ? AutoApprovalMode.AUTO_EDIT - : AutoApprovalMode.SUGGEST; + : config.approvalMode || AutoApprovalMode.SUGGEST; preloadModels(); diff --git a/codex-cli/src/utils/config.ts b/codex-cli/src/utils/config.ts index 45dd9bb5..84676535 100644 --- a/codex-cli/src/utils/config.ts +++ b/codex-cli/src/utils/config.ts @@ -77,6 +77,7 @@ export type AppConfig = { apiKey?: string; model: string; instructions: string; + approvalMode?: AutoApprovalMode; fullAutoErrorMode?: FullAutoErrorMode; memory?: MemoryConfig; /** Whether to enable desktop notifications for responses */ @@ -275,6 +276,7 @@ export const loadConfig = ( : DEFAULT_AGENTIC_MODEL), instructions: combinedInstructions, notify: storedConfig.notify === true, + approvalMode: storedConfig.approvalMode, safeCommands: storedConfig.safeCommands ?? [], }; @@ -391,6 +393,7 @@ export const saveConfig = ( // Create the config object to save const configToSave: StoredConfig = { model: config.model, + approvalMode: config.approvalMode, }; // Add history settings if they exist diff --git a/codex-cli/tests/config.test.tsx b/codex-cli/tests/config.test.tsx index 024db853..49b3229b 100644 --- a/codex-cli/tests/config.test.tsx +++ b/codex-cli/tests/config.test.tsx @@ -1,6 +1,7 @@ import type * as fsType from "fs"; import { loadConfig, saveConfig } from "../src/utils/config.js"; // parent import first +import { AutoApprovalMode } from "../src/utils/auto-approval-mode.js"; import { tmpdir } from "os"; import { join } from "path"; import { test, expect, beforeEach, afterEach, vi } from "vitest"; @@ -107,3 +108,43 @@ test("loads user instructions + project doc when codex.md is present", () => { userInstr + "\n\n--- project-doc ---\n\n" + projectDoc, ); }); + +test("loads and saves approvalMode correctly", () => { + // Setup config with approvalMode + memfs[testConfigPath] = JSON.stringify( + { + model: "mymodel", + approvalMode: AutoApprovalMode.AUTO_EDIT, + }, + null, + 2, + ); + memfs[testInstructionsPath] = "test instructions"; + + // Load config and verify approvalMode + const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, { + disableProjectDoc: true, + }); + + // Check approvalMode was loaded correctly + expect(loadedConfig.approvalMode).toBe(AutoApprovalMode.AUTO_EDIT); + + // Modify approvalMode and save + const updatedConfig = { + ...loadedConfig, + approvalMode: AutoApprovalMode.FULL_AUTO, + }; + + saveConfig(updatedConfig, testConfigPath, testInstructionsPath); + + // Verify saved config contains updated approvalMode + expect(memfs[testConfigPath]).toContain( + `"approvalMode": "${AutoApprovalMode.FULL_AUTO}"`, + ); + + // Load again and verify updated value + const reloadedConfig = loadConfig(testConfigPath, testInstructionsPath, { + disableProjectDoc: true, + }); + expect(reloadedConfig.approvalMode).toBe(AutoApprovalMode.FULL_AUTO); +});