deny unknown fields and add docs

This commit is contained in:
Sayan Sisodiya
2026-01-08 19:10:12 -08:00
parent d47b85d9fe
commit eede02a5ef
8 changed files with 81 additions and 25 deletions

View File

@@ -0,0 +1,12 @@
# Config JSON Schema
We generate a JSON Schema for `~/.codex/config.toml` from the `ConfigToml` type
and commit it at `docs/config.schema.json` for editor integration.
When you change any fields included in `ConfigToml` (or nested config types),
regenerate the schema and update the fixture:
```
just write-config-schema
cargo test -p codex-core config_schema_matches_fixture
```

View File

@@ -684,8 +684,13 @@ pub fn set_default_oss_provider(codex_home: &Path, provider: &str) -> std::io::R
}
/// Base config deserialized from ~/.codex/config.toml.
// The JSON Schema in `docs/config.schema.json` is generated from this struct.
// If you add, remove, or rename fields, regenerate the schema via
// `just write-config-schema` and update the fixture test.
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ConfigToml {
pub test: Option<String>,
/// Optional override of model selection.
pub model: Option<String>,
/// Review model override used by the `/review` feature.
@@ -886,6 +891,7 @@ impl From<ConfigToml> for UserSavedConfig {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ProjectConfig {
pub trust_level: Option<TrustLevel>,
}
@@ -901,6 +907,7 @@ impl ProjectConfig {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ToolsToml {
#[serde(default, alias = "web_search_request")]
pub web_search: Option<bool>,
@@ -920,6 +927,7 @@ impl From<ToolsToml> for Tools {
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct GhostSnapshotToml {
/// Exclude untracked files larger than this many bytes from ghost snapshots.
#[serde(alias = "ignore_untracked_files_over_bytes")]

View File

@@ -12,6 +12,7 @@ use codex_protocol::openai_models::ReasoningEffort;
/// Collection of common configuration options that a user can define as a unit
/// in `config.toml`.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ConfigProfile {
pub model: Option<String>,
/// The key in the `model_providers` map identifying the

View File

@@ -48,7 +48,7 @@ pub(crate) fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
.properties
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());
}
validation.additional_properties = Some(Box::new(schema_gen.subschema_for::<bool>()));
validation.additional_properties = Some(Box::new(Schema::Bool(false)));
object.object = Some(Box::new(validation));
Schema::Object(object)
@@ -84,6 +84,7 @@ fn mcp_server_schema(schema_gen: &mut SchemaGenerator) -> Schema {
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct McpServerStdioSchema {
command: String,
#[serde(default)]
@@ -109,6 +110,7 @@ struct McpServerStdioSchema {
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
struct McpServerStreamableHttpSchema {
url: String,
#[serde(default)]
@@ -145,9 +147,13 @@ mod tests {
let fixture = std::fs::read_to_string(fixture_path).expect("read config schema fixture");
let fixture_value: serde_json::Value =
serde_json::from_str(&fixture).expect("parse config schema fixture");
assert_eq!(fixture_value, schema_value);
assert_eq!(
fixture_value, schema_value,
"Current schema for `config.toml` doesn't match the fixture. Run `just write-config-schema` to overwrite with your changes."
);
}
/// Overwrite the config schema fixture with the current schema.
#[test]
#[ignore]
fn write_config_schema_fixture() {

View File

@@ -256,6 +256,7 @@ impl UriBasedFileOpener {
/// Settings that govern if and what will be written to `~/.codex/history.jsonl`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct History {
/// If true, history entries will not be written to disk.
pub persistence: HistoryPersistence,
@@ -279,6 +280,7 @@ pub enum HistoryPersistence {
/// Analytics settings loaded from config.toml. Fields are optional so we can apply defaults.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct AnalyticsConfigToml {
/// When `false`, disables analytics across Codex product surfaces in this profile.
pub enabled: Option<bool>,
@@ -302,6 +304,7 @@ pub enum OtelHttpProtocol {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
#[serde(rename_all = "kebab-case")]
pub struct OtelTlsConfig {
pub ca_certificate: Option<AbsolutePathBuf>,
@@ -311,6 +314,7 @@ pub struct OtelTlsConfig {
/// Which OTEL exporter to use.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
#[serde(rename_all = "kebab-case")]
pub enum OtelExporterKind {
None,
@@ -334,6 +338,7 @@ pub enum OtelExporterKind {
/// OTEL settings loaded from config.toml. Fields are optional so we can apply defaults.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct OtelConfigToml {
/// Log user prompt in traces
pub log_user_prompt: Option<bool>,
@@ -407,6 +412,7 @@ impl Default for ScrollInputMode {
/// Collection of settings that are specific to the TUI.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct Tui {
/// Enable desktop notifications from the TUI when the terminal is unfocused.
/// Defaults to `true`.
@@ -546,6 +552,7 @@ const fn default_true() -> bool {
/// (primarily the Codex IDE extension). NOTE: these are different from
/// notifications - notices are warnings, NUX screens, acknowledgements, etc.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct Notice {
/// Tracks whether the user has acknowledged the full access warning prompt.
pub hide_full_access_warning: Option<bool>,
@@ -569,6 +576,7 @@ impl Notice {
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct SandboxWorkspaceWrite {
#[serde(default)]
pub writable_roots: Vec<AbsolutePathBuf>,
@@ -609,6 +617,7 @@ pub enum ShellEnvironmentPolicyInherit {
/// Policy for building the `env` when spawning a process via either the
/// `shell` or `local_shell` tool.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ShellEnvironmentPolicyToml {
pub inherit: Option<ShellEnvironmentPolicyInherit>,

View File

@@ -50,6 +50,7 @@ pub enum WireApi {
/// Serializable representation of a provider definition.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ModelProviderInfo {
/// Friendly display name.
pub name: String,

View File

@@ -118,9 +118,7 @@
"type": "boolean"
}
},
"additionalProperties": {
"type": "boolean"
}
"additionalProperties": false
},
"file_opener": {
"description": "Optional URI-based file opener. If set, citations to files in the model output will be hyperlinked using the specified URI scheme.",
@@ -371,6 +369,7 @@
"type": "boolean"
}
},
"additionalProperties": false,
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
@@ -384,7 +383,8 @@
"description": "When `false`, disables analytics across Codex product surfaces in this profile.",
"type": "boolean"
}
}
},
"additionalProperties": false
},
"AskForApproval": {
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
@@ -521,9 +521,7 @@
"type": "boolean"
}
},
"additionalProperties": {
"type": "boolean"
}
"additionalProperties": false
},
"include_apply_patch_tool": {
"type": "boolean"
@@ -556,7 +554,8 @@
"tools_web_search": {
"type": "boolean"
}
}
},
"additionalProperties": false
},
"ForcedLoginMethod": {
"type": "string",
@@ -582,7 +581,8 @@
"type": "integer",
"format": "int64"
}
}
},
"additionalProperties": false
},
"History": {
"description": "Settings that govern if and what will be written to `~/.codex/history.jsonl`.",
@@ -605,7 +605,8 @@
}
]
}
}
},
"additionalProperties": false
},
"HistoryPersistence": {
"oneOf": [
@@ -626,6 +627,7 @@
]
},
"McpServerStdioSchema": {
"description": "Schema-only representations of MCP server configs.\n\nKeep these in sync with the custom MCP config parsing in `crate::config::types::McpServerConfig`.",
"type": "object",
"required": [
"command"
@@ -693,7 +695,8 @@
"type": "number",
"format": "double"
}
}
},
"additionalProperties": false
},
"McpServerStreamableHttpSchema": {
"type": "object",
@@ -756,7 +759,8 @@
"url": {
"type": "string"
}
}
},
"additionalProperties": false
},
"ModelProviderInfo": {
"description": "Serializable representation of a provider definition.",
@@ -838,7 +842,8 @@
}
]
}
}
},
"additionalProperties": false
},
"Notice": {
"description": "Settings for notices we display to users via the tui and app-server clients (primarily the Codex IDE extension). NOTE: these are different from notifications - notices are warnings, NUX screens, acknowledgements, etc.",
@@ -872,7 +877,8 @@
"type": "string"
}
}
}
},
"additionalProperties": false
},
"Notifications": {
"anyOf": [
@@ -941,7 +947,8 @@
}
]
}
}
},
"additionalProperties": false
},
"OtelExporterKind": {
"description": "Which OTEL exporter to use.",
@@ -987,7 +994,8 @@
}
]
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
@@ -1022,7 +1030,8 @@
}
]
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
@@ -1059,7 +1068,8 @@
"client-private-key": {
"$ref": "#/definitions/AbsolutePathBuf"
}
}
},
"additionalProperties": false
},
"ProjectConfig": {
"type": "object",
@@ -1067,7 +1077,8 @@
"trust_level": {
"$ref": "#/definitions/TrustLevel"
}
}
},
"additionalProperties": false
},
"ReasoningEffort": {
"description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning",
@@ -1131,7 +1142,8 @@
"$ref": "#/definitions/AbsolutePathBuf"
}
}
}
},
"additionalProperties": false
},
"ScrollInputMode": {
"description": "How TUI2 should interpret mouse scroll events.\n\nTerminals generally encode both mouse wheels and trackpads as the same \"scroll up/down\" mouse button events, without a magnitude. This setting controls whether Codex uses a heuristic to infer wheel vs trackpad per stream, or forces a specific behavior.",
@@ -1217,7 +1229,8 @@
"type": "string"
}
}
}
},
"additionalProperties": false
},
"ToolsToml": {
"type": "object",
@@ -1231,7 +1244,8 @@
"default": null,
"type": "boolean"
}
}
},
"additionalProperties": false
},
"TrustLevel": {
"description": "Represents the trust level for a project directory. This determines the approval policy and sandbox mode applied.",
@@ -1320,7 +1334,8 @@
"default": true,
"type": "boolean"
}
}
},
"additionalProperties": false
},
"UriBasedFileOpener": {
"oneOf": [

View File

@@ -56,3 +56,7 @@ build-for-release:
# Run the MCP server
mcp-server-run *args:
cargo run -p codex-mcp-server -- "$@"
# Regenerate the json schema for config.toml from the current config types.
write-config-schema:
cargo test -p codex-core write_config_schema_fixture -- --ignored