Files
codex/codex-rs/core/src/config_loader
Michael Bolin a70aee1a1e Fix Windows Bazel app-server trust tests (#16711)
## Why

Extracted from [#16528](https://github.com/openai/codex/pull/16528) so
the Windows Bazel app-server test failures can be reviewed independently
from the rest of that PR.

This PR targets:

-
`suite::v2::thread_shell_command::thread_shell_command_runs_as_standalone_turn_and_persists_history`
-
`suite::v2::thread_start::thread_start_with_elevated_sandbox_trusts_project_and_followup_loads_project_config`
-
`suite::v2::thread_start::thread_start_with_nested_git_cwd_trusts_repo_root`

There were two Windows-specific assumptions baked into those tests and
the underlying trust lookup:

- project trust keys were persisted and looked up using raw path
strings, but Bazel's Windows test environment can surface canonicalized
paths with `\\?\` / UNC prefixes or normalized symlink/junction targets,
so follow-up `thread/start` requests no longer matched the project entry
that had just been written
- `item/commandExecution/outputDelta` assertions compared exact trailing
line endings even though shell output chunk boundaries and CRLF handling
can differ on Windows, and Bazel made that timing-sensitive mismatch
visible

There was also one behavior bug separate from the assertion cleanup:
`thread/start` decided whether to persist trust from the final resolved
sandbox policy, but on Windows an explicit `workspace-write` request may
be downgraded to `read-only`. That incorrectly skipped writing trust
even though the request had asked to elevate the project, so the new
logic also keys off the requested sandbox mode.

## What

- Canonicalize project trust keys when persisting/loading `[projects]`
entries, while still accepting legacy raw keys for existing configs.
- Persist project trust when `thread/start` explicitly requests
`workspace-write` or `danger-full-access`, even if the resolved policy
is later downgraded on Windows.
- Make the Windows app-server tests compare persisted trust paths and
command output deltas in a path/newline-normalized way.

## Verification

- Existing app-server v2 tests cover the three failing Windows Bazel
cases above.
2026-04-03 21:41:25 +00:00
..

codex-core config loader

This module is the canonical place to load and describe Codex configuration layers (user config, CLI/session overrides, managed config, and MDM-managed preferences) and to produce:

  • An effective merged TOML config.
  • Per-key origins metadata (which layer “wins” for a given key).
  • Per-layer versions (stable fingerprints) used for optimistic concurrency / conflict detection.

Public surface

Exported from codex_core::config_loader:

  • load_config_layers_state(codex_home, cwd_opt, cli_overrides, overrides, cloud_requirements) -> ConfigLayerStack
  • ConfigLayerStack
    • effective_config() -> toml::Value
    • origins() -> HashMap<String, ConfigLayerMetadata>
    • layers_high_to_low() -> Vec<ConfigLayer>
    • with_user_config(user_config) -> ConfigLayerStack
  • ConfigLayerEntry (one layers {name, config, version, disabled_reason}; name carries source metadata)
  • LoaderOverrides (test/override hooks for managed config sources)
  • merge_toml_values(base, overlay) (public helper used elsewhere)

Layering model

Precedence is top overrides bottom:

  1. MDM managed preferences (macOS only)
  2. System managed config (e.g. managed_config.toml)
  3. Session flags (CLI overrides, applied as dotted-path TOML writes)
  4. User config (config.toml)

Layers with a disabled_reason are still surfaced for UI, but are ignored when computing the effective config and origins metadata. This is what ConfigLayerStack::effective_config() implements.

Typical usage

Most callers want the effective config plus metadata:

use codex_core::config_loader::{load_config_layers_state, LoaderOverrides};
use codex_utils_absolute_path::AbsolutePathBuf;
use toml::Value as TomlValue;

let cli_overrides: Vec<(String, TomlValue)> = Vec::new();
let cwd = AbsolutePathBuf::current_dir()?;
let layers = load_config_layers_state(
    &codex_home,
    Some(cwd),
    &cli_overrides,
    LoaderOverrides::default(),
    None,
).await?;

let effective = layers.effective_config();
let origins = layers.origins();
let layers_for_ui = layers.layers_high_to_low();

Internal layout

Implementation is split by concern:

  • state.rs: public types (ConfigLayerEntry, ConfigLayerStack) + merge/origins convenience methods.
  • layer_io.rs: reading config.toml, managed config, and managed preferences inputs.
  • overrides.rs: CLI dotted-path overrides → TOML “session flags” layer.
  • merge.rs: recursive TOML merge.
  • fingerprint.rs: stable per-layer hashing and per-key origins traversal.
  • macos.rs: managed preferences integration (macOS only).