User-Level vs Project-Level Codex Configuration: A Complete Guide

User-level Codex configuration provides global defaults stored in ~/.codex, while project-level configuration overrides these settings with repository-specific values stored in the current working directory.

The openai/codex-plugin-cc repository distinguishes between two configuration layers to give developers both global consistency and per-project flexibility. Understanding how user-level and project-level Codex configuration interact is essential for managing default models, API endpoints, and security gates across different repositories.

What is User-Level Codex Configuration?

User-level configuration acts as the global baseline for every Codex session on your machine. These settings apply when you run Codex commands in any directory, unless overridden by project-specific values.

Location and Scope

According to the source code in codex.mjs, the CLI resolves the home directory using the resolveCodexHome() helper at lines 54-55. By default, this points to ~/.codex (or the path specified by the CODEX_HOME environment variable).

This directory stores global defaults including:

  • Default model provider and model selection
  • API key locations and authentication status
  • Shared runtime endpoints
  • Global feature flags like stopReviewGate

How It Is Resolved

The resolveCodexHome() function scans for configuration files such as external_agent_session_imports.json and other hidden files stored in the user-level directory. These values serve as the fallback when a project does not supply its own overrides.

What is Project-Level Codex Configuration?

Project-level configuration lives alongside your code and provides repository-specific settings that override the global defaults for the duration of the command.

Location and Scope

Project-level settings are typically stored in JSON files within the current working directory (such as codex.project.json in the repository root). These files control:

  • Per-project sandbox modes (read-only vs read-write)
  • Custom provider aliases
  • Repository-specific model preferences
  • Feature flags like stopReviewGate that apply only to the current codebase

How It Is Loaded

When a command runs, the client sends a client.request("config/read", { cwd }) request to the app-server, as implemented in codex.mjs at lines 871-876. The server loads the project-specific configuration file (if present) and returns it, allowing the client to merge these values with the user-level defaults.

How the Configuration Layers Merge

Codex follows a specific precedence order when resolving configuration values:

  1. User-level defaults are loaded first from CODEX_HOME
  2. Project-level overrides are loaded from the current working directory via the config/read request
  3. Merged result is produced by the getConfig() function in state.mjs, which plugins and hooks consume

A concrete example of this merge logic appears in stop-review-gate-hook.mjs at line 146. The hook calls getConfig(workspaceRoot) to fetch the merged configuration and activates the review gate only when the project explicitly sets {"stopReviewGate": true}. If the project-level config omits this key, the hook falls back to the user-level default (typically false).

Configuration Examples

Setting Global User-Level Defaults

Create a global configuration file in your Codex home directory:


# Create the directory if it doesn't exist

mkdir -p "$(codex home)"  # Resolves to ~/.codex by default

# Write global configuration

cat > "$(codex home)/config.json" <<EOF
{
  "model_provider": "openai",
  "model": "gpt-4o-mini",
  "stopReviewGate": false,
  "sandbox": "read-only"
}
EOF

This file is read by resolveCodexHome() (lines 54-55 of codex.mjs) and applies to all projects unless overridden.

Configuring Project-Level Overrides

Add a repository-specific configuration file in your project root:

// codex.project.json in the repository root
{
  "stopReviewGate": true,
  "model": "gpt-4o",
  "sandbox": "read-write",
  "custom_provider": "azure-east"
}

When you run a Codex command inside this directory, the app-server executes the config/read request (lines 871-876 of codex.mjs) and merges these values with the global defaults. The project-level model and sandbox settings take precedence over the user-level configuration.

Accessing Merged Configuration in Plugins

Plugins consume the merged configuration through the getConfig utility:

// In your plugin or hook file
import { getConfig } from "./state.mjs";

function enforceSecurityPolicy(workspaceRoot) {
  const cfg = getConfig(workspaceRoot);  // Merges user- and project-level
  
  if (cfg.stopReviewGate) {
    // Activate review gate for this repo only
    console.log("Review gate enabled via project config");
  }
  
  // Use the effective model (project overrides user)
  const effectiveModel = cfg.model;
}

This pattern is demonstrated in stop-review-gate-hook.mjs, which uses the merged configuration to determine whether to enforce review policies for the specific repository.

Summary

  • User-level configuration resides in CODEX_HOME (default ~/.codex) and provides global defaults for all Codex sessions on the machine, resolved via resolveCodexHome() in codex.mjs.
  • Project-level configuration lives in the current working directory and overrides user-level settings for that specific repository, loaded via the config/read request (lines 871-876 of codex.mjs).
  • Merge logic combines both layers with project-level taking precedence, implemented in state.mjs through the getConfig() function.
  • Practical implementation appears in hooks like stop-review-gate-hook.mjs (line 146), which checks the merged configuration to determine feature activation.

Frequently Asked Questions

Where does Codex store user-level configuration files?

Codex stores user-level configuration in the directory specified by CODEX_HOME, which defaults to ~/.codex on Unix systems. The source code resolves this path through the resolveCodexHome() helper function located at lines 54-55 of codex.mjs. You can override this location by setting the CODEX_HOME environment variable before running Codex commands.

Can project-level configuration completely replace user-level settings?

Project-level configuration overrides specific keys while inheriting others from the user-level defaults. When the app-server processes a config/read request (as implemented in codex.mjs lines 871-876), it returns only the project-specific values. The client then merges these with the user-level configuration, meaning undefined project-level keys fall back to global defaults rather than being unset.

How do I check which configuration values are active in my current project?

Access the merged configuration programmatically using the getConfig(workspaceRoot) function exported from state.mjs. This utility merges the user-level baseline with any project-level overrides found in the current working directory. The merged result reflects exactly which values the Codex CLI and plugins will use for the current session, including inherited defaults from ~/.codex.

What happens if a configuration key exists in both layers?

When a key exists in both user-level and project-level configuration, the project-level value takes precedence. For example, if ~/.codex/config.json specifies "model": "gpt-4o-mini" but your repository's project config specifies "model": "gpt-4o", Codex will use gpt-4o when running commands in that directory. This precedence is handled by the merge logic in state.mjs and consumed by hooks like stop-review-gate-hook.mjs at line 146.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →