Embed defaults in the packaged config layer (#38179)

## What changed

- Add an embedded `defaults.toml` and always install it as the lowest-precedence configuration layer when no packaged-defaults path is supplied.
- Keep packaged defaults out of config RPC layer and origin metadata.
- Report a user setting as overridden only when the effective layer has higher precedence, so clearing a setting can fall back to its packaged default without a false override.

## Testing

- Cover loading the packaged layer when other configuration files are absent.
- Cover config RPC filtering and clearing a user value that falls back to a packaged default.

GitOrigin-RevId: c1c0e1e681e4ac1be2602550972533443c5df51b
This commit is contained in:
jif
2026-08-12 12:15:47 +00:00
committed by copyberry
parent 8270a7c74d
commit 0e82c62a44
7 changed files with 87 additions and 26 deletions
@@ -143,7 +143,10 @@ impl ConfigManager {
.map_err(|err| ConfigManagerError::json("failed to deserialize configuration", err))?;
let mut origins = layers.origins();
origins.retain(|path, _| {
origins.retain(|path, metadata| {
if matches!(&metadata.name, ConfigLayerSource::PackagedDefaults { .. }) {
return false;
}
let segments = path.split('.').map(str::to_string).collect::<Vec<_>>();
layers
.requirements_toml()
@@ -160,6 +163,9 @@ impl ConfigManager {
layers: params.include_layers.then(|| {
layers
.all_layers_high_to_low()
.filter(|layer| {
!matches!(&layer.name, ConfigLayerSource::PackagedDefaults { .. })
})
.map(|layer| config_layer_to_api(layer.as_layer()))
.collect()
}),
@@ -818,10 +824,8 @@ fn compute_override_metadata(
effective: &TomlValue,
segments: &[String],
) -> Option<OverriddenMetadata> {
let user_value = match layers.get_active_user_layer() {
Some(user_layer) => value_at_semantic_path(&user_layer.config, segments),
None => return None,
};
let user_layer = layers.get_active_user_layer()?;
let user_value = value_at_semantic_path(&user_layer.config, segments);
let effective_value = value_at_semantic_path(effective, segments);
if user_value.is_some() && user_value == effective_value {
@@ -833,6 +837,9 @@ fn compute_override_metadata(
}
let overriding_layer = find_effective_layer(layers, segments)?;
if overriding_layer.name.precedence() <= user_layer.name.precedence() {
return None;
}
let message = override_message(&overriding_layer.name);
Some(OverriddenMetadata {
@@ -174,6 +174,29 @@ async fn clear_missing_nested_config_is_noop() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn clearing_user_setting_falls_back_to_packaged_default_without_override() -> Result<()> {
let tmp = tempdir()?;
let path = tmp.path().join(CONFIG_TOML_FILE);
std::fs::write(&path, "hide_agent_reasoning = true\n")?;
let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf());
let response = service
.write_value(ConfigValueWriteParams {
file_path: Some(path.display().to_string()),
key_path: "hide_agent_reasoning".to_string(),
value: serde_json::Value::Null,
merge_strategy: MergeStrategy::Replace,
expected_version: None,
})
.await?;
assert_eq!(response.status, WriteStatus::Ok);
assert_eq!(response.overridden_metadata, None);
assert_eq!(std::fs::read_to_string(&path)?, "");
Ok(())
}
#[tokio::test]
async fn clear_user_value_if_matches_clears_matching_value() -> Result<()> {
let tmp = tempdir().expect("tempdir");
@@ -270,6 +270,11 @@ sandbox_mode = "workspace-write"
profile: None,
}
);
assert!(
origins
.values()
.all(|origin| !matches!(&origin.name, ConfigLayerSource::PackagedDefaults { .. }))
);
let layers = layers.expect("layers present");
assert_layers_user_then_optional_system(&layers, user_file)?;
+1
View File
@@ -2,5 +2,6 @@ load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "config",
compile_data = ["defaults.toml"],
crate_name = "codex_config",
)
+17
View File
@@ -0,0 +1,17 @@
# Fixed defaults for packaged Codex clients.
include_permissions_instructions = true
include_apps_instructions = true
include_collaboration_mode_instructions = true
include_environment_context = true
cli_auth_credentials_store = "file"
mcp_oauth_credentials_store = "auto"
project_doc_max_bytes = 32768
project_doc_fallback_filenames = []
background_terminal_max_timeout = 300000
file_opener = "vscode"
hide_agent_reasoning = false
chatgpt_base_url = "https://chatgpt.com/backend-api/"
project_root_markers = [".git"]
[history]
persistence = "save-all"
+17 -4
View File
@@ -159,12 +159,25 @@ pub async fn load_config_layers_state(
),
)
})?;
Some(ConfigLayerEntry::new(
ConfigLayerEntry::new(
ConfigLayerSource::PackagedDefaults { file: file.clone() },
resolve_relative_paths_in_config_toml(config, base_dir)?,
))
)
} else {
None
let file = AbsolutePathBuf::from_absolute_path(std::env::current_exe()?)?;
let raw_toml = include_str!("../../defaults.toml");
let config = toml::from_str(raw_toml).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid embedded packaged defaults; this is a Codex build error: {error}"),
)
})?;
ConfigLayerEntry::new_with_raw_toml(
ConfigLayerSource::PackagedDefaults { file },
config,
raw_toml.to_owned(),
AbsolutePathBuf::from_absolute_path(codex_home)?,
)
};
let active_user_profile = overrides.user_config_profile.clone();
let ignore_managed_requirements = overrides.ignore_managed_requirements;
@@ -250,7 +263,7 @@ pub async fn load_config_layers_state(
.map_err(io::Error::other)?;
let mut layers = Vec::<ConfigLayerEntry>::new();
layers.extend(packaged_defaults_layer);
layers.push(packaged_defaults_layer);
let cli_overrides_layer = if cli_overrides.is_empty() {
None
+12 -17
View File
@@ -831,7 +831,7 @@ async fn managed_goal_token_budget_overrides_user_config() -> anyhow::Result<()>
}
#[tokio::test]
async fn returns_empty_when_all_layers_missing() {
async fn returns_packaged_defaults_when_other_layers_are_missing() {
let tmp = tempdir().expect("tempdir");
let managed_path = tmp.path().join("managed_config.toml");
@@ -865,12 +865,11 @@ async fn returns_empty_when_all_layers_missing() {
"expected empty config for user layer when config.toml does not exist"
);
let binding = layers.effective_config();
let base_table = binding.as_table().expect("base table expected");
assert!(
base_table.is_empty(),
"expected empty base layer when configs missing"
);
let packaged_defaults = layers
.layers_low_to_high()
.find(|layer| matches!(layer.name, ConfigLayerSource::PackagedDefaults { .. }))
.expect("packaged defaults layer should always be present");
assert_eq!(layers.effective_config(), packaged_defaults.config);
let num_system_layers = layers
.layers_high_to_low()
.filter(|layer| matches!(layer.name, ConfigLayerSource::System { .. }))
@@ -879,16 +878,6 @@ async fn returns_empty_when_all_layers_missing() {
num_system_layers, 1,
"system layer should always be present"
);
#[cfg(not(target_os = "macos"))]
{
let effective = layers.effective_config();
let table = effective.as_table().expect("top-level table expected");
assert!(
table.is_empty(),
"expected empty table when configs missing"
);
}
}
#[tokio::test]
@@ -1004,6 +993,9 @@ async fn includes_thread_config_layers_in_stack() -> anyhow::Result<()> {
ConfigLayerSource::System {
file: expected_system_config,
},
ConfigLayerSource::PackagedDefaults {
file: AbsolutePathBuf::from_absolute_path(std::env::current_exe()?)?,
},
]
);
assert_eq!(
@@ -2322,6 +2314,9 @@ model_provider = "cloud-provider"
.map(|layer| layer.name.clone())
.collect::<Vec<_>>(),
vec![
ConfigLayerSource::PackagedDefaults {
file: AbsolutePathBuf::from_absolute_path(std::env::current_exe()?)?,
},
ConfigLayerSource::System {
file: AbsolutePathBuf::from_absolute_path(&system_config_path)?,
},