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

> Understand user-level vs project-level Codex configuration. Learn how global defaults in ~/.codex and repository-specific overrides in your project improve your workflow.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: deep-dive
- Published: 2026-07-12

---

**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`](https://github.com/openai/codex-plugin-cc/blob/main/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`](https://github.com/openai/codex-plugin-cc/blob/main/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:

```bash

# 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:

```json
// 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:

```javascript
// 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.