Skip to content

config: add strict config parsing#20559

Merged
bolinfest merged 1 commit into
mainfrom
pr20559
May 13, 2026
Merged

config: add strict config parsing#20559
bolinfest merged 1 commit into
mainfrom
pr20559

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented May 1, 2026

Why

Codex intentionally ignores unknown config.toml fields by default so older and newer config files keep working across versions. That leniency also makes typo detection hard because misspelled or misplaced keys disappear silently.

This change adds an opt-in strict config mode so users and tooling can fail fast on unrecognized config fields without changing the default permissive behavior.

This feature is possible because serde_ignored exposes the exact signal Codex needs: it lets Codex run ordinary Serde deserialization while recording fields Serde would otherwise ignore. That avoids requiring #[serde(deny_unknown_fields)] across every config type and keeps strict validation opt-in around the existing config model.

What Changed

Added strict config validation

  • Added serde_ignored-based validation for ConfigToml in codex-rs/config/src/strict_config.rs.
  • Combined serde_ignored with serde_path_to_error so strict mode preserves typed config error paths while also collecting fields Serde would otherwise ignore.
  • Added strict-mode validation for unknown [features] keys, including keys that would otherwise be accepted by FeaturesToml's flattened boolean map.
  • Kept typed config errors ahead of ignored-field reporting, so malformed known fields are reported before unknown-field diagnostics.
  • Added source-range diagnostics for top-level and nested unknown config fields, including non-file managed preference source names.

Kept parsing single-pass per source

  • Reworked file and managed-config loading so strict validation reuses the already parsed TomlValue for that source.
  • For actual config files and managed config strings, the loader now reads once, parses once, and validates that same parsed value instead of deserializing multiple times.
  • Validated -c / --config override layers with the same base-directory context used for normal relative-path resolution, so unknown override keys are still reported when another override contains a relative path.

Scoped --strict-config to config-heavy entry points

  • Added support for --strict-config on the main config-loading entry points where it is most useful:
    • codex
    • codex resume
    • codex fork
    • codex exec
    • codex review
    • codex mcp-server
    • codex app-server when running the server itself
    • the standalone codex-app-server binary
    • the standalone codex-exec binary
  • Commands outside that set now reject --strict-config early with targeted errors instead of accepting it everywhere through shared CLI plumbing.
  • codex app-server subcommands such as proxy, daemon, and generate-* are intentionally excluded from the first rollout.
  • When app-server strict mode sees invalid config, app-server exits with the config error instead of logging a warning and continuing with defaults.
  • Introduced a dedicated ReviewCommand wrapper in codex-rs/cli instead of extending shared ReviewArgs, so --strict-config stays on the outer config-loading command surface and does not become part of the reusable review payload used by codex exec review.

Coverage

  • Added tests for top-level and nested unknown config fields, unknown [features] keys, typed-error precedence, source-location reporting, and non-file managed preference source names.
  • Added CLI coverage showing invalid --enable, invalid --disable, and unknown -c overrides still error when --strict-config is present, including compound-looking feature names such as multi_agent_v2.subagent_usage_hint_text.
  • Added integration coverage showing both codex app-server --strict-config and standalone codex-app-server --strict-config exit with an error for unknown config fields instead of starting with fallback defaults.
  • Added coverage showing unsupported command surfaces reject --strict-config with explicit errors.

Example Usage

Run Codex with strict config validation enabled:

codex --strict-config

Strict config mode is also available on the supported config-heavy subcommands:

codex --strict-config exec "explain this repository"
codex review --strict-config --uncommitted
codex mcp-server --strict-config
codex app-server --strict-config --listen off
codex-app-server --strict-config --listen off

For example, if ~/.codex/config.toml contains a typo in a key name:

model = "gpt-5"
approval_polic = "on-request"

then codex --strict-config reports the misspelled key instead of silently ignoring it. The path is shortened to ~ here for readability:

$ codex --strict-config
Error loading config.toml:
~/.codex/config.toml:2:1: unknown configuration field `approval_polic`
  |
2 | approval_polic = "on-request"
  | ^^^^^^^^^^^^^^

Without --strict-config, Codex keeps the existing permissive behavior and ignores the unknown key.

Strict config mode also validates ad-hoc -c / --config overrides:

$ codex --strict-config -c foo=bar
Error: unknown configuration field `foo` in -c/--config override

$ codex --strict-config -c features.foo=true
Error: unknown configuration field `features.foo` in -c/--config override

Invalid feature toggles are rejected too, including values that look like nested config paths:

$ codex --strict-config --enable does_not_exist
Error: Unknown feature flag: does_not_exist

$ codex --strict-config --disable does_not_exist
Error: Unknown feature flag: does_not_exist

$ codex --strict-config --enable multi_agent_v2.subagent_usage_hint_text
Error: Unknown feature flag: multi_agent_v2.subagent_usage_hint_text

Unsupported commands reject the flag explicitly:

$ codex --strict-config cloud list
Error: `--strict-config` is not supported for `codex cloud`

Verification

The codex-cli strict_config tests cover invalid --enable, invalid --disable, the compound multi_agent_v2.subagent_usage_hint_text case, unknown -c overrides, app-server strict startup failure through codex app-server, and rejection for unsupported commands such as codex cloud, codex mcp, codex remote-control, and codex app-server proxy.

The config and config-loader tests cover unknown top-level fields, unknown nested fields, unknown [features] keys, source-location reporting, non-file managed config sources, and -c validation for keys such as features.foo.

The app-server test suite covers standalone codex-app-server --strict-config startup failure for an unknown config field.

Documentation

The Codex CLI docs on developers.openai.com/codex should mention --strict-config as an opt-in validation mode for supported config-heavy entry points once this ships.

@bolinfest bolinfest requested a review from a team as a code owner May 1, 2026 04:28
@bolinfest bolinfest force-pushed the pr20559 branch 9 times, most recently from 9475d37 to d4d74a4 Compare May 1, 2026 18:31
@bolinfest bolinfest force-pushed the pr20559 branch 9 times, most recently from bbec96c to 999cbdb Compare May 1, 2026 23:19
@bolinfest bolinfest requested review from jif-oai and pakrym-oai May 1, 2026 23:21
@bolinfest
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 999cbdb177

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/config/src/loader/mod.rs
Copy link
Copy Markdown
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is doing too much plumbing by hand for a config-loading mode

Comment thread codex-rs/exec/src/lib.rs Outdated
Comment thread codex-rs/utils/cli/src/config_override.rs Outdated
@bolinfest bolinfest force-pushed the pr20559 branch 3 times, most recently from b909ad9 to 7726a0d Compare May 4, 2026 16:51
@bolinfest
Copy link
Copy Markdown
Collaborator Author

@jif-oai I addressed the config-loading plumbing concern in the latest pushes. Strict mode now flows through ConfigBuilder::strict_config(...) into ConfigLoadOptions, and the command crates use CliConfigOverrides::prepend_root_overrides(...) so strict config is applied before config loading instead of each caller hand-plumbing LoaderOverrides-style state.

I also rebased again onto current main; the PR head is now 7726a0d.

@bolinfest bolinfest requested a review from jif-oai May 4, 2026 19:39
Copy link
Copy Markdown
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm still not convinced by all this wiring. It makes it super easy to forget one entry-point and have some blind spots

Maybe the proper solution is a cleaning of the config system though which would make it out of scope of this PR

I can approve to un-block but I think we need to re-design something here tbh

Comment thread codex-rs/cli/src/main.rs Outdated
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.harness_overrides(overrides)
.strict_config(root_config_overrides.strict_config)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only makes features list strict. features enable/disable --strict-config still bypass config loading..

let strict_config = config_overrides.strict_config;

match subcommand {
MarketplaceSubcommand::Add(args) => run_add(args).await?,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, why don't we have strict for add/remove?
Would be good to add a small comment in the code to justify

Ok(())
}

async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) -> Result<()> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about run_remove for example?

@bolinfest
Copy link
Copy Markdown
Collaborator Author

@jif-oai I addressed your above comments by changing things so that subcommands have to declare explicit support for --strict-config. Not all commands support it, but we can expand the list as necessary.

Copy link
Copy Markdown
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for me but one point to note:
It does not hard fail the app-server. Is this expected? I just see the following but the app-server still starts and work

2026-05-13T08:55:41.283598Z ERROR codex_app_server: Invalid configuration; using defaults. com.openai.codex:config_toml_base64:11:10: unknown configuration field `features.general_analytics`

@bolinfest
Copy link
Copy Markdown
Collaborator Author

It does not hard fail the app-server. Is this expected? I just see the following but the app-server still starts and work

Good catch! I wasn't accounting for this fallback login in codex app-server:

let message = config_warning_from_error("Invalid configuration; using defaults.", &err);
config_warnings.push(message);
(
config_manager.load_default_config().await.map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("error loading default config after config error: {e}"),
)
})?,
false,
)

I added the following to the top of that block:

if strict_config {
    return Err(err);
}

Then I also discovered that codex-app-server the standalone binary has its own main() and args so I updated codex-rs/app-server/src/main.rs to add strict_config to AppServerArgs.

And then I added the following integration tests:

codex-rs/app-server/tests/suite/strict_config.rs
codex-rs/cli/tests/app_server.rs

@bolinfest bolinfest enabled auto-merge (squash) May 13, 2026 15:51
@bolinfest bolinfest merged commit 889ee01 into main May 13, 2026
46 of 59 checks passed
@bolinfest bolinfest deleted the pr20559 branch May 13, 2026 16:08
@github-actions github-actions Bot locked and limited conversation to collaborators May 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants