mirror of
https://github.com/openai/codex.git
synced 2026-05-03 02:46:39 +00:00
Fixed config merging issue with profiles (#9509)
This PR fixes a small issue with chained (layered) config.toml file merging. The old logic didn't properly handle profiles. In particular, if a lower-layer config overrides a profile defined in a higher-layer config, the override did not take effect. This prevents users from having project-specific profile overrides and contradicts the (soon-to-be) documented behavior of config merging. The change adds a unit test for this case. It also exposes a function from the config crate that is needed by the app server code paths to implement support for layered configs.
This commit is contained in:
@@ -399,6 +399,7 @@ pub struct ConfigBuilder {
|
||||
cli_overrides: Option<Vec<(String, TomlValue)>>,
|
||||
harness_overrides: Option<ConfigOverrides>,
|
||||
loader_overrides: Option<LoaderOverrides>,
|
||||
fallback_cwd: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ConfigBuilder {
|
||||
@@ -422,21 +423,29 @@ impl ConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fallback_cwd(mut self, fallback_cwd: Option<PathBuf>) -> Self {
|
||||
self.fallback_cwd = fallback_cwd;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> std::io::Result<Config> {
|
||||
let Self {
|
||||
codex_home,
|
||||
cli_overrides,
|
||||
harness_overrides,
|
||||
loader_overrides,
|
||||
fallback_cwd,
|
||||
} = self;
|
||||
let codex_home = codex_home.map_or_else(find_codex_home, std::io::Result::Ok)?;
|
||||
let cli_overrides = cli_overrides.unwrap_or_default();
|
||||
let harness_overrides = harness_overrides.unwrap_or_default();
|
||||
let mut harness_overrides = harness_overrides.unwrap_or_default();
|
||||
let loader_overrides = loader_overrides.unwrap_or_default();
|
||||
let cwd = match harness_overrides.cwd.as_deref() {
|
||||
let cwd_override = harness_overrides.cwd.as_deref().or(fallback_cwd.as_deref());
|
||||
let cwd = match cwd_override {
|
||||
Some(path) => AbsolutePathBuf::try_from(path)?,
|
||||
None => AbsolutePathBuf::current_dir()?,
|
||||
};
|
||||
harness_overrides.cwd = Some(cwd.to_path_buf());
|
||||
let config_layer_stack =
|
||||
load_config_layers_state(&codex_home, Some(cwd), &cli_overrides, loader_overrides)
|
||||
.await?;
|
||||
@@ -2301,6 +2310,52 @@ trust_level = "trusted"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn project_profile_overrides_user_profile() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let workspace = TempDir::new()?;
|
||||
let workspace_key = workspace.path().to_string_lossy().replace('\\', "\\\\");
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
format!(
|
||||
r#"
|
||||
profile = "global"
|
||||
|
||||
[profiles.global]
|
||||
model = "gpt-global"
|
||||
|
||||
[profiles.project]
|
||||
model = "gpt-project"
|
||||
|
||||
[projects."{workspace_key}"]
|
||||
trust_level = "trusted"
|
||||
"#,
|
||||
),
|
||||
)?;
|
||||
let project_config_dir = workspace.path().join(".codex");
|
||||
std::fs::create_dir_all(&project_config_dir)?;
|
||||
std::fs::write(
|
||||
project_config_dir.join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
profile = "project"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
cwd: Some(workspace.path().to_path_buf()),
|
||||
..Default::default()
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.active_profile.as_deref(), Some("project"));
|
||||
assert_eq!(config.model.as_deref(), Some("gpt-project"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_sandbox_mode_overrides_base() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
Reference in New Issue
Block a user