chore: include User layer in ConfigLayerStack even if config.toml is empty (#8456)

This is necessary so that `$CODEX_HOME/skills` and `$CODEX_HOME/rules`
still get loaded even if `$CODEX_HOME/config.toml` does not exist. See
#8453.

For now, it is possible to omit this layer when creating a dummy
`ConfigLayerStack` in a test. We can revisit that later, if it turns out
to be the right thing to do.
This commit is contained in:
Michael Bolin
2025-12-22 16:40:26 -08:00
committed by GitHub
parent f6275a5142
commit 14dbd0610a
3 changed files with 33 additions and 10 deletions

View File

@@ -101,7 +101,7 @@ pub async fn load_config_layers_state(
// exists, but is malformed, then this error should be propagated to the
// user.
let user_file = AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, codex_home)?;
match tokio::fs::read_to_string(&user_file).await {
let user_layer = match tokio::fs::read_to_string(&user_file).await {
Ok(contents) => {
let user_config: TomlValue = toml::from_str(&contents).map_err(|e| {
io::Error::new(
@@ -123,13 +123,18 @@ pub async fn load_config_layers_state(
})?;
let user_config =
resolve_relative_paths_in_config_toml(user_config, user_config_parent)?;
layers.push(ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
user_config,
));
ConfigLayerEntry::new(ConfigLayerSource::User { file: user_file }, user_config)
}
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
if e.kind() == io::ErrorKind::NotFound {
// If there is no config.toml file, record an empty entry
// for this user layer, as this may still have subfolders
// that are significant in the overall ConfigLayerStack.
ConfigLayerEntry::new(
ConfigLayerSource::User { file: user_file },
TomlValue::Table(toml::map::Map::new()),
)
} else {
return Err(io::Error::new(
e.kind(),
format!(
@@ -139,7 +144,8 @@ pub async fn load_config_layers_state(
));
}
}
}
};
layers.push(user_layer);
if let Some(cwd) = cwd {
let mut merged_so_far = TomlValue::Table(toml::map::Map::new());

View File

@@ -88,9 +88,24 @@ async fn returns_empty_when_all_layers_missing() {
)
.await
.expect("load layers");
assert!(
layers.get_user_layer().is_none(),
"no user layer when CODEX_HOME/config.toml does not exist"
let user_layer = layers
.get_user_layer()
.expect("expected a user layer even when CODEX_HOME/config.toml does not exist");
assert_eq!(
&ConfigLayerEntry {
name: super::ConfigLayerSource::User {
file: AbsolutePathBuf::resolve_path_against_base(CONFIG_TOML_FILE, tmp.path())
.expect("resolve user config.toml path")
},
config: TomlValue::Table(toml::map::Map::new()),
version: version_for_toml(&TomlValue::Table(toml::map::Map::new())),
},
user_layer,
);
assert_eq!(
user_layer.config,
TomlValue::Table(toml::map::Map::new()),
"expected empty config for user layer when config.toml does not exist"
);
let binding = layers.effective_config();