Prevent Configuration Pollution: Separate `projects.xxxx.trusted_level` from `config.toml`

Open 💬 16 comments Opened Mar 13, 2026 by milanglacier
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

CLI

What subscription do you have?

Plus

What platform is your computer?

Darwin 25.1.0 arm64 arm

What issue are you seeing?

When opening a project for the first time, Codex prompts for user approval. Once granted, the application appends this project-specific metadata directly to ~/.codex/config.toml:

[projects.xxx]
trusted_level='trusted'

This behavior creates friction for users who manage their dotfiles via Git, as config.toml is typically synced across multiple machines. Injecting dynamic trust states and local project paths into the global configuration pollutes version control histories. Furthermore, absolute project paths are machine-specific and should not be tracked in a globally synced configuration file.

What steps can reproduce the bug?

When opening a project for the first time, Codex prompts for user approval. Once granted, the application appends this project-specific metadata directly to ~/.codex/config.toml:

[projects.xxx]
trusted_level='trusted'

What is the expected behavior?

Expected Behavior:

To prevent the pollution of global configuration options, project-related metadata should be decoupled from config.toml. Instead, Codex should store this state in a dedicated file within the ~/.codex directory—such as projects.toml or projects_state.toml—specifically designated for project information:

[projects.xxx]
trusted_level='trusted'

Additional information

_No response_

View original on GitHub ↗

16 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #14547
  • #14599

Powered by Codex Action

keramblock · 4 months ago

at least add glob support please

caelaxie · 4 months ago

+1. trust state should be separate from config.toml. mixing repo-specific local state into synced dotfiles is pretty annoying.

flexdinesh · 3 months ago

I use Codex on multiple dev machines and manage my dotfiles using stow. Codex config makes it really difficult to manage the dotfiles. Every time I use codex in a new repo, changes show up in my config that's not compatible with the config in the other machine. Please separate user config and tooling config (like projects that are auto updated by codex) into separate files.

Here's an example of how I shared my opencode config with multiple machines because machine-specific state is not a part of the user config file.

<img width="896" height="1632" alt="Image" src="https://github.com/user-attachments/assets/1ce7fee5-eadd-494b-b1bc-68146e065ecd" />

flexdinesh · 3 months ago

Related issue with more engagement: https://github.com/openai/codex/issues/11061

nkeat12 · 3 months ago

sessions too

coygeek · 2 months ago

+1 — multi-machine dotfiles user (3 Macs, different usernames) hitting exactly this. Leaving concrete evidence in case it helps with prioritization.

Empirical test

codex-cli 0.123.0, macOS 25.4.0. Tried the obvious portable form by hand-editing the shared config:

[projects."~"]
trust_level = "trusted"

Running codex from $HOME still triggers the trust prompt. Approving it appends [projects."/Users/<username>"] back to config.toml — which then waits to be committed to the shared repo on that machine, collide on the next pull from another machine, repeat.

Fix appears localized to a few call sites

The helper already exists and is used elsewhere:

  • AbsolutePathBuf::maybe_expand_home_directory() in codex-rs/utils/absolute-path/src/lib.rs expands ~/ during deserialization for other AbsolutePathBuf-typed fields.

What's missing for trust specifically:

  • project_trust_key() in codex-rs/core/src/config_loader/mod.rs uses raw path.to_string_lossy() — so [projects."~"] never matches $HOME.
  • set_project_trust_level_inner() in codex-rs/config/src/lib.rs writes absolute paths verbatim when Codex auto-approves a trust prompt, so even with read-side expansion the auto-written entry re-introduces a machine-specific path.

The separate-file proposal in this issue would also work. A cheaper alternative that doesn't introduce a new file: apply maybe_expand_home_directory() to the trust-key read path, and have the auto-write path re-canonicalize $HOME back to ~/ on serialization.

Related: #11061 covers the broader "share user preferences across machines" scope, where the same asymmetry also affects marketplace source paths.

colonelpanic8 · 2 months ago

I am hitting the same underlying problem on NixOS with Home Manager/dotfiles, and I think the scope is slightly broader than trusted project paths.

My desired setup is:

  • keep ~/.codex/config.toml as source-controlled user configuration from my dotfiles
  • let Codex keep machine-local mutable state under ~/.codex without dirtying or fighting that file
  • preserve host-specific trust decisions, marketplace cache paths, and other Codex-owned runtime metadata across launches

In practice, config.toml is currently serving as both declarative config and mutable app state. I tried working around this by regenerating ~/.codex/config.toml from a tracked base file plus a harvested local-state fragment, but that approach is brittle because Codex-owned state is not limited to [projects.*] / [tui.*]. On my current install (codex-cli 0.130.0) the mutable block also contains entries like:

[marketplaces.openai-bundled]
last_updated = "..."
source_type = "local"
source = "/home/.../.codex/.tmp/bundled-marketplaces/openai-bundled"

[marketplaces.openai-primary-runtime]
last_updated = "..."
source_type = "local"
source = "/home/.../.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"

Those absolute paths clearly should not live in a shared dotfiles config, but if a dotfiles manager replaces config.toml, Codex can lose state it expects to find there.

A dedicated machine-local state file would make this much easier to manage. For example:

  • ~/.codex/config.toml: user-authored/source-controlled config
  • ~/.codex/state.toml or ~/.codex/projects.toml: Codex-owned mutable local state
  • documented precedence/merge behavior between the two

That would also make it reasonable for users to symlink or Home-Manager-manage config.toml directly, without needing fragile extraction/merge scripts around Codex writes.

thesimonho · 2 months ago

+1 on the projects trust boundary bleeding over into a configuration file

but this also applies to hooks and their approval state:

[hooks.state."/home/simon/.codex/hooks.json:post_tool_use:0:0"]
trusted_hash = "sha256:eae3e..."
mshakeg · 2 months ago

Hitting this on codex-cli 0.130.0. The underlying problem is broader than just [projects.X]~/.codex/config.toml is the sink for several distinct kinds of machine-local mutable state today:

  • [projects."<abs path>"] — per-project trust
  • [hooks.state] — hook trust hashes (confirming @thesimonho's earlier comment that this has the same shape)
  • [notice] flags like hide_<...>_migration_prompt and [notice.model_migrations] — written when the user dismisses model-migration prompts
  • [tui.model_availability_nux] — counters for new-model intro screens

Each of these is keyed by absolute path or per-user UI state, mutates on a routine user action, and lives in the same file as configuration the user actually wants to share or VCS-manage. @colonelpanic8 framed this well above — the goal is to "let Codex keep machine-local mutable state" out of source-controlled config.

A targeted fix that only relocated [projects.X] would leave the other three lurking. The cleaner principle: config.toml is read-only-by-Codex aside from explicit user edits, and everything Codex writes back from a UI/CLI action lives in a separate machine-local file (e.g. ~/.codex/state.toml). That makes the file VCS-manageable and shareable without per-table workarounds.

The split proposed in #15433 (project trust → ~/.codex/trust_level.toml) generalizes naturally if every machine-local concern gets its own file, or all of them land under one ~/.codex/state.toml. Happy to test whatever direction this goes.

thomak-dev · 1 month ago

My config.toml being filled with garbage everytime it is opened:

[marketplaces.openai-bundled]
last_updated = "2026-05-28T20:02:15Z"
source_type = "local"
source = '\\?\C:\Users\XXX\.codex\.tmp\bundled-marketplaces\openai-bundled'

[plugins."browser@openai-bundled"]
enabled = true

[features]
js_repl = false

[mcp_servers.node_repl]
args = []
command = 'C:\Users\XXX\AppData\Local\OpenAI\Codex\bin\3c238e29bbc930ff\node_repl.exe'
startup_timeout_sec = 120

[mcp_servers.node_repl.env]
NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS = "1000"
NODE_REPL_NODE_MODULE_DIRS = ""
NODE_REPL_NODE_PATH = 'C:\Users\XXX\AppData\Local\OpenAI\Codex\bin\5b9024f90663758b\node.exe'
NODE_REPL_TRUSTED_CODE_PATHS = 'C:\Users\XXX\.codex'
CODEX_HOME = 'C:\Users\XXX\.codex'
NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "496c7b3cb95b4bc20cff49b513150606e0da0000c92bf752206bee5a6c248423"
BROWSER_USE_AVAILABLE_BACKENDS = "iab"
BROWSER_USE_MARKETPLACE_NAME = "openai-bundled"
NODE_REPL_UNTRUSTED_ENV_ALLOWLIST = "BROWSER_USE_MARKETPLACE_NAME"
CODEX_CLI_PATH = 'C:\Users\XXX\AppData\Local\OpenAI\Codex\bin\958d608b5e0546a5\codex.exe'

It's like you guys are using config.toml as a dump for everything that requires saving to disk....

dmccaffery · 1 month ago

Please consider supporting the XDG Base Directory Specification. This makes it so much easier to reason about configuration, state, user data, etc, from a dotfiles and backup perspective.

cedws · 1 month ago

Just wanted to share my own experience/reasons for wanting this: I'm using Nix on macOS to manage my dotfiles and unfortunately I get an error every time I start Codex due to it trying to write back to the config.toml which is not writable by design.

luisnquin · 1 month ago

If you're in Nix then you can patch the codex package while this is being resolved.

diff --git a/config/src/config_toml.rs b/config/src/config_toml.rs
index 7270c3f329..e2029b8ad3 100644
--- a/config/src/config_toml.rs
+++ b/config/src/config_toml.rs
@@ -862,6 +862,15 @@ fn normalize_project_lookup_key(key: String) -> String {
 fn project_config_for_lookup_key(
     projects: &HashMap<String, ProjectConfig>,
     lookup_key: &str,
+) -> Option<ProjectConfig> {
+    Path::new(lookup_key)
+        .ancestors()
+        .find_map(|path| project_config_for_exact_lookup_key(projects, &path.to_string_lossy()))
+}
+
+fn project_config_for_exact_lookup_key(
+    projects: &HashMap<String, ProjectConfig>,
+    lookup_key: &str,
 ) -> Option<ProjectConfig> {
     if let Some(project_config) = projects.get(lookup_key) {
         return Some(project_config.clone());
diff --git a/config/src/loader/mod.rs b/config/src/loader/mod.rs
index d154f87e1d..14bfde2a4e 100644
--- a/config/src/loader/mod.rs
+++ b/config/src/loader/mod.rs
@@ -1052,6 +1052,15 @@ fn normalize_project_trust_lookup_key(key: String) -> String {
 fn project_trust_for_lookup_key(
     projects_trust: &std::collections::HashMap<String, TrustLevel>,
     lookup_key: &str,
+) -> Option<(String, TrustLevel)> {
+    Path::new(lookup_key).ancestors().find_map(|path| {
+        project_trust_for_exact_lookup_key(projects_trust, &path.to_string_lossy())
+    })
+}
+
+fn project_trust_for_exact_lookup_key(
+    projects_trust: &std::collections::HashMap<String, TrustLevel>,
+    lookup_key: &str,
 ) -> Option<(String, TrustLevel)> {
     if let Some(trust_level) = projects_trust.get(lookup_key).copied() {
         return Some((lookup_key.to_string(), trust_level));
programs.codex = {
	enable = true;
	package = pkgs.codex.overrideAttrs (old: {
	    patches = (old.patches or []) ++ [
	      ./recursive-project-trust.patch
	    ];
	});
};
yfguo · 1 month ago

I want to restate the use case here because I think the current design misses a very common dotfiles workflow.

Users need a global configuration file for portable preferences. For example: model defaults, reasoning effort, approval policy, sandbox defaults, UI preferences, notification behavior,
feature flags, MCP preferences, etc. These are not project-specific settings. They are user preferences, and users reasonably want to bring them along when moving to a different
machine.

What many people do today is standard dotfiles practice:

  1. Keep config.toml in a git-tracked dotfiles repo.
  2. Symlink it back to ~/.codex/config.toml.
  3. Sync it across machines.

The problem is that Codex also stores non-portable, machine-local mutable state in that same file. Examples include:

  • trusted project directories
  • hook trust state / hashes
  • local marketplace/cache paths
  • generated Desktop/MCP/runtime paths
  • UI dismissal / NUX state

Some of this state may not cause a functional problem when copied to another machine, but it still creates a bad user experience. For a git-tracked config.toml, these changes are meaningless churn. They dirty the dotfiles repo, create diffs that do not represent intentional configuration changes, and add friction every time Codex records local state.

The response that ~/.codex/config.toml is “not designed to be used across machines” does not really answer the issue. If that file is not meant to be portable, then what file is supposed to hold global portable user preferences?

Right now the choices seem to be:

  1. Put common preferences in every project-local .codex/config.toml, which is portable but repetitive and inappropriate for personal preferences.
  2. Put common preferences in ~/.codex/config.toml, which is global but apparently “not designed” to be portable and is constantly rewritten by Codex with machine-local state.

That leaves a clear product gap: Codex has project-local config and machine-local config/state, but no clean place for portable global user preferences.

Judging from the number of related issues and comments, this is not an edge case. It is a common frustration among users who manage their development environment with dotfiles, Nix/Home Manager, stow, or any similar setup.

Proposed fix:

Option 1, preferred: keep ~/.codex/config.toml as the user-authored portable global config, and move Codex-owned mutable local state into a separate file, for example:

``text
~/.codex/config.toml # user-authored portable preferences
~/.codex/state.toml # Codex-owned machine-local mutable state
``

Trust state, hook approval state, local runtime paths, marketplace cache paths, notice dismissal state, and similar generated values should live in the state file, not in the portable
config.

Option 2: if OpenAI strongly wants to keep ~/.codex/config.toml as machine-local, then please introduce a new portable global settings file, for example:

  ~/.codex/settings.toml

But then Codex has to define precedence between settings.toml, config.toml, project config, and CLI overrides. That seems more complicated and more disruptive than Option 1.

I would genuinely like to understand the reasoning behind the current design. Right now it looks like config.toml is being used as both declarative user configuration and an application state dump. Those are different concerns, and combining them makes Codex unnecessarily painful to use with standard dotfiles workflows.

faevourite · 5 days ago

I would also personally be ok with something like ~/.codex/config-shared.toml being introduced with lower precedence to the existing ~/.codex/config.toml (so local overrides in config.toml win). Then I would just update my dotfiles to manage config-shared.toml instead of config.toml. Might be an easier migration now that config.toml is so polluted it may as well be state.toml.