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-onlyvsread-write) - Custom provider aliases
- Repository-specific model preferences
- Feature flags like
stopReviewGatethat 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:
- User-level defaults are loaded first from
CODEX_HOME - Project-level overrides are loaded from the current working directory via the
config/readrequest - Merged result is produced by the
getConfig()function instate.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 viaresolveCodexHome()incodex.mjs. - Project-level configuration lives in the current working directory and overrides user-level settings for that specific repository, loaded via the
config/readrequest (lines 871-876 ofcodex.mjs). - Merge logic combines both layers with project-level taking precedence, implemented in
state.mjsthrough thegetConfig()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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →