# User-Level vs Project-Level Codex Configuration: Understanding the Hierarchy in openai/codex-plugin-cc

> Understand the hierarchy of user-level and project-level Codex configuration in openai/codex-plugin-cc. Learn how project configs override global defaults for your workspace.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: understanding
- Published: 2026-08-02

---

**User-level Codex configuration provides global defaults stored in `~/.codex`, while project-level configuration resides in the repository and overrides those defaults for that specific workspace.**

The `openai/codex-plugin-cc` repository implements a two-tier configuration system that balances machine-wide preferences with repository-specific requirements. Understanding how **user-level and project-level Codex configuration** interact is essential for managing default models, API endpoints, and security gates across different development environments.

## Where Configuration Lives: Files and Paths

### User-Level Configuration (CODEX_HOME)

The user-level layer lives in a dedicated home directory controlled by the `CODEX_HOME` environment variable, which defaults to `~/.codex`. In `plugins/codex/scripts/lib/codex.mjs`, the helper function `resolveCodexHome()` (lines 54-55) resolves this path and confirms its existence before the CLI reads global defaults. This directory stores 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 configuration files that apply to every Codex session on the machine.

### Project-Level Configuration (CWD)

Project-level settings reside inside the current working directory—typically in a JSON file that the Codex app-server discovers via a `config/read` request. When a command executes, the client sends `client.request("config/read", { cwd })` to the server (implemented in `codex.mjs` lines 871-876), which locates and returns the repository-specific configuration. These settings override the global baseline only for the duration of the command within that directory.

## How Configuration Layers Are Merged

The resolution process follows a strict hierarchy. First, the CLI invokes `resolveCodexHome()` to establish the user-level baseline from `~/.codex`. Then, if a project configuration file exists in the workspace root, the app-server intercepts the `config/read` request (handled in `app-server.mjs`) and returns the project-specific JSON. Finally, the `getConfig(workspaceRoot)` utility from `state.mjs` merges these layers, with project values taking precedence over user defaults.

A concrete example is the **review-gate** flag. The `stop-review-gate-hook.mjs` hook (line 146) calls `getConfig(workspaceRoot)` to fetch the merged configuration. If the project-level config contains `{"stopReviewGate": true}`, the hook activates the gate for that repository only; otherwise, it falls back to the user-level default (typically `false`).

## Practical Configuration Examples

### Setting User-Level Configuration (Global)

Configure defaults that apply across all projects on your machine:

```bash

# Create the global config directory if it doesn't exist

mkdir -p "$(codex home)"            # Resolves to ~/.codex via resolveCodexHome()

# Write global defaults

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

```

*The `codex home` command relies on `resolveCodexHome()` defined in `codex.mjs` (lines 54-55).*

### Adding Project-Level Configuration (Per-Repository)

Create a configuration file in your repository root to override global settings:

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

```

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.

### Accessing Merged Configuration from a Plugin

Use the `state.mjs` module to retrieve the fully resolved configuration within hooks or plugins:

```javascript
import { getConfig } from "./state.mjs";

function someHook(workspaceRoot) {
  const cfg = getConfig(workspaceRoot);   // Merges user- and project-level
  if (cfg.stopReviewGate) {
    // Enforce review gate for this repo only
    console.log("Review gate active: stopping for approval");
  }
}

```

*This pattern is demonstrated in `stop-review-gate-hook.mjs`, which checks project-level flags before falling back to user defaults.*

## Key Implementation Files

Understanding these source files clarifies how the configuration hierarchy operates:

- **`codex.mjs`** (`plugins/codex/scripts/lib/codex.mjs`): Contains `resolveCodexHome()` for locating the user directory and issues the `config/read` request for project files.
- **`state.mjs`** (`plugins/codex/scripts/lib/state.mjs`): Provides `getConfig()`, which merges user-level and project-level settings for consumption by plugins.
- **`app-server.mjs`** (`plugins/codex/scripts/lib/app-server.mjs`): Handles the underlying `client.request("config/read", …)` that pulls project-level JSON from the working directory.
- **`stop-review-gate-hook.mjs`** (`plugins/codex/scripts/stop-review-gate-hook.mjs`): Example consumer that demonstrates project-level flag override behavior.
- **`args.mjs`** (`plugins/codex/scripts/lib/args.mjs`): Demonstrates how CLI options seed from the merged configuration layer.

## Summary

- **User-level configuration** lives in `~/.codex` (or `CODEX_HOME`) and provides global defaults via `resolveCodexHome()`.
- **Project-level configuration** resides in the repository and is fetched via the `config/read` request (lines 871-876 of `codex.mjs`).
- The `getConfig()` utility in `state.mjs` merges layers, with project settings overriding user defaults.
- Security features like `stopReviewGate` demonstrate this hierarchy: project settings activate per-repo gates, while user settings provide the machine-wide fallback.

## Frequently Asked Questions

### What takes precedence if both configurations define the same setting?

**Project-level values always override user-level defaults.** When `getConfig(workspaceRoot)` executes in `state.mjs`, it merges the project-specific JSON (returned by the `config/read` request) on top of the user-level baseline, ensuring repository-specific requirements take priority.

### How does the Codex CLI locate the user-level configuration directory?

The CLI calls `resolveCodexHome()` defined in `codex.mjs` (lines 54-55), which checks the `CODEX_HOME` environment variable and falls back to `~/.codex`. This function ensures the directory exists before the client attempts to read global configuration files.

### Can project-level configuration affect security settings like review gates?

**Yes.** The `stop-review-gate-hook.mjs` (line 146) specifically checks the merged configuration for `stopReviewGate`. If the project config sets this to `true`, the gate activates for that repository only, regardless of the user-level default. This allows teams to enforce mandatory code reviews on a per-project basis.

### Where should I store sensitive API keys in Codex configuration?

Store sensitive credentials in the **user-level** configuration within `~/.codex`, not in project-level files that might be committed to version control. The `codex.mjs` utilities read authentication status from the user home directory, keeping secrets out of shared repositories while allowing the project config to reference provider aliases or endpoints.