Skip to content

Warn on invalid config enum values#21111

Open
aibrahim-oai wants to merge 50 commits into
mainfrom
config-lenient-enum-warnings
Open

Warn on invalid config enum values#21111
aibrahim-oai wants to merge 50 commits into
mainfrom
config-lenient-enum-warnings

Conversation

@aibrahim-oai
Copy link
Copy Markdown
Collaborator

@aibrahim-oai aibrahim-oai commented May 5, 2026

Why

A single invalid enum-valued setting in config.toml should not make the whole assembled config unusable. The rest of the config may still contain valid model, sandbox, profile, tool, hook, or telemetry settings that Codex can safely apply.

Review feedback also pushed the design away from making invalid values representable throughout ConfigToml. The raw invalid state should stay at the config boundary: warn from the TOML that is about to be loaded, then continue handing normal typed config values to runtime consumers.

What Changed

  • Added a schema-driven invalid enum warning pass in codex-rs/config/src/enum_warnings.rs.
  • Exposed that pass as invalid_enum_warnings, so call sites use the same vocabulary: detect invalid enum leaves and return advisory warnings.
  • The warning pass walks a TOML value alongside the generated config schema through properties, additionalProperties, items, $ref, allOf, anyOf, and oneOf.
  • Startup loading runs the warning pass over the final merged TOML. Config lock replay runs it over the raw [config] subtree before typed lockfile deserialization.
  • The pass is advisory and non-mutating. It reports invalid enum-like leaves without rewriting the TOML that is later deserialized.
  • Added narrow field-level fallbacks for enum leaves that warnings can report, including root/profile settings, project trust levels, tool suggestion entry types, MCP tool approvals, tool config, hook handler type, model_providers.*.wire_api, and OTEL exporters/protocols.
  • Kept broader shape/type validation with Serde. For example, invalid hook variants are dropped after warning, but malformed known hook variants still fail normally.
  • Kept app-server config writes strict for newly edited enum values only, so stale tolerated enum values do not block unrelated writes.
  • Kept relative path resolution from hiding invalid raw enum values: only resolved absolute path strings are copied back into the raw TOML shape.
  • Regenerated codex-rs/core/config.schema.json for the config shape/default updates.

Verification

Added focused coverage for:

  • schema traversal across refs, composition, arrays, dynamic maps, and tagged unions;
  • valid sibling oneOf variants and non-string union branches not warning incorrectly;
  • invalid root, flattened TUI, nested tool, profile, project, tool-suggestion, MCP, hook, model-provider, and OTEL enum values warning without poisoning unrelated config;
  • hook invalid variant warnings remaining non-blocking while known malformed hook variants stay strict;
  • config lock replay surfacing invalid enum warnings from the raw lockfile config;
  • app-server config writes rejecting newly supplied invalid enum values while allowing unrelated edits when stale enum values already exist;
  • relative-path resolution preserving invalid raw enum values until warning collection;
  • startup integration emitting an invalid enum warning while valid settings still reach session configuration.

Ran just write-config-schema after the schema-affecting changes. Local test execution is intentionally left to PR CI per repo guidance for online PRs.

Allow config loading to continue when enum-valued settings contain invalid values. Invalid enum entries are removed from the layer before merging and surfaced through startup config warnings, while unrelated valid settings keep loading normally.

Co-authored-by: Codex <noreply@openai.com>
@aibrahim-oai aibrahim-oai requested a review from a team as a code owner May 5, 2026 00:01
aibrahim-oai and others added 3 commits May 5, 2026 03:13
Defer invalid enum handling until after config layers are assembled. This keeps layer loading raw, removes invalid enum values from the final effective config, and reports warnings with the dotted field path and invalid value only.

Co-authored-by: Codex <noreply@openai.com>
Replace the explicit config enum sanitizer with a generic deserialize retry loop over the assembled TOML. Unknown enum variant errors remove the offending field, append a warning with the field path and invalid value, and retry deserialization.

Co-authored-by: Codex <noreply@openai.com>
Store selected config enum fields as Lenient<T> so invalid values remain visible after deserialization and can be reported as startup warnings while valid consumers unwrap at runtime. Remove the older retry-loop sanitizer now that warnings come from the typed config tree.

Co-authored-by: Codex <noreply@openai.com>
aibrahim-oai and others added 4 commits May 5, 2026 03:49
Cover the full ConfigBuilder load path for invalid enum values so startup warnings are asserted alongside valid config that still applies.

Co-authored-by: Codex <noreply@openai.com>
Remove the explicit invalid_enum_warnings tree walk and have Lenient::into_valid append warning messages when invalid values are consumed. Keep higher-level resolvers returning values instead of accepting active profile and warning sink plumbing.

Co-authored-by: Codex <noreply@openai.com>
Keep Lenient::into_valid as the silent projection helper and add into_valid_with_warning for runtime config consumption that should emit startup warnings.

Co-authored-by: Codex <noreply@openai.com>
Remove the local warning unwrap helpers so runtime config consumption calls Lenient::into_valid_with_warning directly instead of hiding the API behind wrappers.

Co-authored-by: Codex <noreply@openai.com>
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

config_toml.cli_auth_credentials_store.unwrap_or_default(),

P1 Badge Unwrap the Lenient credential store before use

cli_auth_credentials_store is now Option<Lenient<AuthCredentialsStoreMode>>, but this call still uses unwrap_or_default() as if it were Option<AuthCredentialsStoreMode>. Lenient<T> has no Default, so codex-exec no longer compiles when this path is checked. Convert through into_valid()/warning handling before passing it on.

ℹ️ 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".

aibrahim-oai and others added 4 commits May 5, 2026 04:18
Update the TUI config consumer to unwrap the lenient CLI auth credential store before passing it to the cloud requirements loader.

Co-authored-by: Codex <noreply@openai.com>
Fix the exec cloud requirements path after cli_auth_credentials_store became lenient.

Co-authored-by: Codex <noreply@openai.com>
Address clippy and compile failures from wrapping config enums in Lenient.

Co-authored-by: Codex <noreply@openai.com>
Update test expectations after invalid enum values no longer fail whole config deserialization.

Co-authored-by: Codex <noreply@openai.com>
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 don't think this is the good layer to integrate this

Comment thread codex-rs/config/src/config_toml.rs Outdated
Comment thread codex-rs/config/src/lenient.rs Outdated
Comment thread codex-rs/core/src/config/mod.rs
Comment thread codex-rs/config/src/config_toml.rs Outdated
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: b1c09a4426

ℹ️ 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/exec/src/lib.rs Outdated
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: e1d8a67d5d

ℹ️ 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/app-server/src/config_manager_service_tests.rs Outdated
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: e3de954486

ℹ️ 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 on lines +116 to 117
#[serde_as(deserialize_as = "DefaultOnError")]
pub approval_policy: Option<AskForApproval>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Route ConfigToml parsing through the lenient layer merge

Adding DefaultOnError here makes every direct try_into::<ConfigToml>() silently drop invalid enum values. Callers like app-server config read/write still deserialize the raw flattened effective_config(), so a user approval_policy = "bogus" over a managed never is returned/validated as unset instead of falling back per layer or being rejected.

Useful? React with 👍 / 👎.

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.

I think this comment is still right now?
I just tried with approval_policy = { sandbox_approval = true } and this silently become None...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The idea is to do best effort of catching warnings. I catch wrong enum values but it would be harder to catch all ignored failures

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

let resolved_value = resolved_table.get(key).unwrap_or(original_value);

P1 Badge Preserve invalid defaulted enum values through path resolution

This prefers the serialized typed value whenever the key exists in resolved. For non-Option enum fields with defaults, e.g. [tui] notification_method = "loudly", lenient parsing drops the bad value, serialization reintroduces "auto", and this line replaces the original invalid value before the warning pass. The bad config is silently accepted with the default and no startup warning/fallback.

ℹ️ 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/state.rs Outdated
Comment on lines +339 to +340
let (sanitized_layer, _typed_layer, layer_warnings) =
deserialize_with_enum_warnings(normalized_layer)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Avoid validating each layer before effective overrides apply

Deserializing every layer here makes a lower-precedence non-enum error fatal before a higher layer can override it. For example, a system layer with model_context_window = "bad" and a user/session layer with model_context_window = 100000 used to merge to a valid effective config, but now the system layer parse returns an error and aborts startup.

Useful? React with 👍 / 👎.

@aibrahim-oai
Copy link
Copy Markdown
Collaborator Author

@codex review this

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

ℹ️ 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/state.rs
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: f366ac3326

ℹ️ 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/state.rs
@aibrahim-oai
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

let mut config = Config::load_config_with_layer_stack(
LOCAL_FS.as_ref(),
lock_config_toml,
harness_overrides,
codex_home,
lock_config_layer_stack,

P2 Badge Preserve enum warnings when loading config locks

The normal config is scanned for enum warnings before this branch, but the lockfile config is then loaded through a fresh ConfigLayerStack with no warning scan. Since ConfigLockfileToml contains ConfigToml, the new DefaultOnError handling can silently drop invalid enum values from the lockfile with no startup warning, so locked sessions may ignore bad locked settings without notice.

ℹ️ 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 on lines +540 to +541
if let Some(warning) = enum_value_warnings(value).into_iter().next() {
return Err(SerdeError::custom(warning));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Validate only edited enum values on config writes

Because startup now tolerates invalid enum values, a user can have an ignored stale enum in config and still run Codex. This validation scans the whole user/effective config after any edit, so changing an unrelated key is rejected until the stale enum is fixed. The write path comment/intent is to reject newly provided invalid enum values, not block all unrelated writes.

Useful? React with 👍 / 👎.

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: 3f55561327

ℹ️ 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".

) -> Result<(TomlValue, ConfigToml, Vec<String>), toml::de::Error> {
let merged = self.effective_config();
let warnings = invalid_enum_warnings(&merged);
let typed = merged.clone().try_into::<ConfigToml>()?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Make every warned enum non-fatal

Fresh evidence after the earlier nested-enum review: projects.<id>.trust_level and tool_suggest.*[].type still deserialize through strict enum fields. The warning pass will report these invalid values, but this try_into::<ConfigToml>() still returns an error, so startup/config-lock replay abort instead of continuing with a warning.

Useful? React with 👍 / 👎.

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 the direction is better but there are stills edge cases (that will multiply in the future) that makes it not reliable...
A nice macro would be amazing here but I know people don't like macros

Comment on lines +116 to 117
#[serde_as(deserialize_as = "DefaultOnError")]
pub approval_policy: Option<AskForApproval>,
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.

I think this comment is still right now?
I just tried with approval_policy = { sandbox_approval = true } and this silently become None...

// This helper returns ConfigToml directly, so enum warnings are intentionally
// not surfaced here. The full ConfigBuilder path is responsible for
// attaching them to startup warnings.
let (_merged_toml, cfg, _enum_warnings) = config_layer_stack
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 leaves codex exec on a silent path: it calls this helper directly, so invalid enum values get defaulted but the warning never reaches the user. Should we surface the warnings here too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it still consumes the startup warnings. try just codex hi with approval_policy = "potato"

/// Defaults to `true`.
#[serde(default, rename = "notifications")]
#[serde_as(deserialize_as = "DefaultOnError")]
pub notifications: Notifications,
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.

Same issue as before. If I do for example notifications = "xxx", this silently default...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, because it's an enum

#[schemars(deny_unknown_fields)]
pub struct ToolSuggestConfig {
#[serde(default)]
#[serde_as(deserialize_as = "VecSkipError<_>")]
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.

I'm not sure on this one but this seems to silently drop malformed items in the vec. Which is exactly what we don't want right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, because it has enum

),
(
Some(&TomlValue::String("make-it-so".to_string())),
None,
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.

I still don't get why this is not workspace-write? while it is supposed ot be the higher precedence https://github.com/openai/codex/pull/21111/changes#diff-54f9773abe3add13d5213a4465a234a42359e43793dc79d8f4b63bc3ff849418R322

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants