mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Stop loading legacy managed config on Windows (#38947)
## What changed - Ignore the default `CODEX_HOME/managed_config.toml` on Windows and exclude it from local managed-configuration detection. - Emit a startup warning when the deprecated file exists, directing users to `%ProgramData%\OpenAI\Codex\requirements.toml` for enforced settings or `config.toml` for defaults. - Preserve explicit managed-config path overrides and Unix legacy-file support. ## Testing - Add Windows tests covering ignored legacy settings, the startup warning, and managed-configuration detection through `requirements.toml`. GitOrigin-RevId: a61d9d9912b13817ba82807a486c9ed92e49c7bc
This commit is contained in:
@@ -37,6 +37,8 @@ pub(super) struct LoadedConfigLayers {
|
||||
pub managed_config: Option<MangedConfigFromFile>,
|
||||
/// If present, data read from managed preferences (macOS only).
|
||||
pub managed_config_from_mdm: Option<ManagedConfigFromMdm>,
|
||||
/// User-facing warnings discovered while loading managed configuration.
|
||||
pub startup_warnings: Vec<String>,
|
||||
}
|
||||
|
||||
pub(super) async fn load_config_layers_internal(
|
||||
@@ -58,21 +60,54 @@ pub(super) async fn load_config_layers_internal(
|
||||
..
|
||||
} = overrides;
|
||||
|
||||
#[cfg(windows)]
|
||||
let ignore_default_managed_config = managed_config_path.is_none();
|
||||
#[cfg(not(windows))]
|
||||
let ignore_default_managed_config = false;
|
||||
|
||||
let managed_config_path = AbsolutePathBuf::from_absolute_path(
|
||||
managed_config_path.unwrap_or_else(|| managed_config_default_path(codex_home)),
|
||||
)?;
|
||||
|
||||
let managed_config = read_config_from_path(
|
||||
fs,
|
||||
&managed_config_path,
|
||||
/*log_missing_as_info*/ false,
|
||||
strict_config,
|
||||
)
|
||||
.await?
|
||||
.map(|loaded| MangedConfigFromFile {
|
||||
managed_config: loaded,
|
||||
file: managed_config_path.clone(),
|
||||
});
|
||||
#[cfg(windows)]
|
||||
let startup_warnings = if ignore_default_managed_config {
|
||||
let path_uri = PathUri::from_abs_path(&managed_config_path);
|
||||
match fs.get_metadata(&path_uri, /*sandbox*/ None).await {
|
||||
Ok(_) => vec![format!(
|
||||
"Ignoring deprecated managed config file at {}; CODEX_HOME/managed_config.toml is no longer supported on Windows. Use %ProgramData%\\OpenAI\\Codex\\requirements.toml for enforced settings or config.toml for defaults.",
|
||||
managed_config_path.as_path().display()
|
||||
)],
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Vec::new(),
|
||||
Err(err) => {
|
||||
tracing::debug!(
|
||||
error = %err,
|
||||
path = %managed_config_path.as_path().display(),
|
||||
"Failed to check deprecated managed config file"
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
let startup_warnings = Vec::new();
|
||||
|
||||
let managed_config = if ignore_default_managed_config {
|
||||
None
|
||||
} else {
|
||||
read_config_from_path(
|
||||
fs,
|
||||
&managed_config_path,
|
||||
/*log_missing_as_info*/ false,
|
||||
strict_config,
|
||||
)
|
||||
.await?
|
||||
.map(|loaded| MangedConfigFromFile {
|
||||
managed_config: loaded,
|
||||
file: managed_config_path.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let managed_preferences = load_managed_admin_config_layer(
|
||||
@@ -89,6 +124,7 @@ pub(super) async fn load_config_layers_internal(
|
||||
Ok(LoadedConfigLayers {
|
||||
managed_config,
|
||||
managed_config_from_mdm: managed_preferences,
|
||||
startup_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,7 +204,9 @@ fn validate_config_toml_strictly(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the default managed config path.
|
||||
/// Return the legacy managed config path.
|
||||
///
|
||||
/// On Windows, the default path is only checked so callers can warn that it is ignored.
|
||||
pub(super) fn managed_config_default_path(codex_home: &Path) -> PathBuf {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
||||
@@ -94,11 +94,12 @@ async fn first_layer_config_error_from_entries(layers: &[ConfigLayerEntry]) -> O
|
||||
/// - system `/etc/codex/requirements.toml` (Unix) or
|
||||
/// `%ProgramData%\OpenAI\Codex\requirements.toml` (Windows)
|
||||
/// - cloud: enterprise-managed cloud config bundle requirements
|
||||
/// - legacy: managed_config.toml reinterpreted as requirements.toml
|
||||
/// - legacy: `/etc/codex/managed_config.toml` (Unix) reinterpreted as
|
||||
/// requirements.toml
|
||||
/// - admin: managed preferences (*)
|
||||
///
|
||||
/// For backwards compatibility, we also load from
|
||||
/// `managed_config.toml` and map it to `requirements.toml`.
|
||||
/// For backwards compatibility, Unix continues to load
|
||||
/// `/etc/codex/managed_config.toml` and map it to `requirements.toml`.
|
||||
///
|
||||
/// Configuration is built up from multiple layers in the following order:
|
||||
///
|
||||
@@ -233,10 +234,12 @@ pub async fn load_config_layers_state(
|
||||
let loaded_config_layers =
|
||||
layer_io::load_config_layers_internal(fs, codex_home, overrides.clone(), strict_config)
|
||||
.await?;
|
||||
let mut startup_warnings = (!loaded_config_layers.startup_warnings.is_empty())
|
||||
.then(|| loaded_config_layers.startup_warnings.clone());
|
||||
if !ignore_managed_requirements {
|
||||
requirements_layers.extend(system_requirements_layer);
|
||||
requirements_layers.extend(bundle_requirements_layers);
|
||||
// Continue to support the legacy `managed_config.toml` locations as
|
||||
// Continue to support loaded legacy `managed_config.toml` sources as
|
||||
// requirements layers for backwards compatibility.
|
||||
requirements_layers.extend(requirements_layers_from_legacy_scheme(
|
||||
loaded_config_layers.clone(),
|
||||
@@ -353,7 +356,6 @@ pub async fn load_config_layers_state(
|
||||
);
|
||||
}
|
||||
|
||||
let mut startup_warnings = None;
|
||||
if !ignore_project_config && let Some(cwd) = cwd {
|
||||
let mut merged_so_far = TomlValue::Table(toml::map::Map::new());
|
||||
for layer in &layers {
|
||||
@@ -412,7 +414,9 @@ pub async fn load_config_layers_state(
|
||||
)
|
||||
.await?;
|
||||
layers.extend(project_layers.layers);
|
||||
startup_warnings = Some(project_layers.startup_warnings);
|
||||
startup_warnings
|
||||
.get_or_insert_with(Vec::new)
|
||||
.extend(project_layers.startup_warnings);
|
||||
}
|
||||
|
||||
// Add a layer for runtime overrides from the CLI or UI, if any exist.
|
||||
@@ -435,6 +439,7 @@ pub async fn load_config_layers_state(
|
||||
let LoadedConfigLayers {
|
||||
managed_config,
|
||||
managed_config_from_mdm,
|
||||
..
|
||||
} = loaded_config_layers;
|
||||
if let Some(config) = managed_config {
|
||||
let managed_parent = config.file.as_path().parent().ok_or_else(|| {
|
||||
@@ -736,9 +741,26 @@ fn system_requirements_toml_file_with_overrides(
|
||||
/// Filesystem or managed-preference errors are returned so callers can conservatively avoid
|
||||
/// assuming that administrator-controlled configuration is absent.
|
||||
pub fn has_local_managed_configuration(codex_home: &Path) -> io::Result<bool> {
|
||||
if layer_io::managed_config_default_path(codex_home).try_exists()?
|
||||
|| system_requirements_toml_file()?.as_path().try_exists()?
|
||||
{
|
||||
let system_requirements_file = system_requirements_toml_file()?;
|
||||
has_local_managed_configuration_with_system_requirements_path(
|
||||
codex_home,
|
||||
system_requirements_file.as_path(),
|
||||
)
|
||||
}
|
||||
|
||||
fn has_local_managed_configuration_with_system_requirements_path(
|
||||
codex_home: &Path,
|
||||
system_requirements_path: &Path,
|
||||
) -> io::Result<bool> {
|
||||
#[cfg(windows)]
|
||||
let _ = codex_home;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
if layer_io::managed_config_default_path(codex_home).try_exists()? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if system_requirements_path.try_exists()? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -852,6 +874,7 @@ fn requirements_layers_from_legacy_scheme(
|
||||
let LoadedConfigLayers {
|
||||
managed_config,
|
||||
managed_config_from_mdm,
|
||||
..
|
||||
} = loaded_config_layers;
|
||||
|
||||
let layer_count =
|
||||
|
||||
@@ -221,6 +221,82 @@ async fn missing_packaged_defaults_file_returns_an_error() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tokio::test]
|
||||
async fn default_windows_managed_config_is_ignored_with_warning() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let codex_home = tmp.path().join("codex-home");
|
||||
std::fs::create_dir_all(&codex_home).expect("create codex home");
|
||||
let managed_config_path = codex_home.join("managed_config.toml");
|
||||
std::fs::write(
|
||||
&managed_config_path,
|
||||
r#"
|
||||
model = "legacy-model"
|
||||
approval_policy = "never"
|
||||
sandbox_mode = "danger-full-access"
|
||||
"#,
|
||||
)
|
||||
.expect("write default legacy managed config");
|
||||
std::fs::write(codex_home.join(CONFIG_TOML_FILE), r#"model = "user-model""#)
|
||||
.expect("write user config");
|
||||
|
||||
let mut overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
overrides.managed_config_path = None;
|
||||
overrides.system_config_path = Some(tmp.path().join("system-config.toml"));
|
||||
overrides.system_requirements_path = Some(tmp.path().join("requirements.toml"));
|
||||
let stack = load_config_layers_state(
|
||||
&TestFileSystem,
|
||||
&codex_home,
|
||||
/*cwd*/ None,
|
||||
&[],
|
||||
overrides,
|
||||
&crate::NoopThreadConfigLoader,
|
||||
)
|
||||
.await
|
||||
.expect("load config layers");
|
||||
|
||||
assert_eq!(
|
||||
stack.effective_config().get("model"),
|
||||
Some(&TomlValue::String("user-model".to_string()))
|
||||
);
|
||||
assert_eq!(stack.requirements_toml().allowed_approval_policies, None);
|
||||
assert_eq!(stack.requirements_toml().allowed_sandbox_modes, None);
|
||||
assert!(stack.all_layers_low_to_high().all(|layer| !matches!(
|
||||
&layer.name,
|
||||
ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. }
|
||||
)));
|
||||
let expected_warnings = vec![format!(
|
||||
"Ignoring deprecated managed config file at {}; CODEX_HOME/managed_config.toml is no longer supported on Windows. Use %ProgramData%\\OpenAI\\Codex\\requirements.toml for enforced settings or config.toml for defaults.",
|
||||
managed_config_path.display()
|
||||
)];
|
||||
assert_eq!(stack.startup_warnings(), Some(expected_warnings.as_slice()));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn windows_local_managed_configuration_ignores_legacy_file_but_detects_requirements() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let codex_home = tmp.path().join("codex-home");
|
||||
std::fs::create_dir_all(&codex_home).expect("create codex home");
|
||||
std::fs::write(codex_home.join("managed_config.toml"), "")
|
||||
.expect("write default legacy managed config");
|
||||
let system_requirements_path = tmp.path().join("requirements.toml");
|
||||
|
||||
let legacy_only = has_local_managed_configuration_with_system_requirements_path(
|
||||
&codex_home,
|
||||
&system_requirements_path,
|
||||
)
|
||||
.expect("check legacy-only managed configuration");
|
||||
std::fs::write(&system_requirements_path, "").expect("write system requirements");
|
||||
let with_system_requirements = has_local_managed_configuration_with_system_requirements_path(
|
||||
&codex_home,
|
||||
&system_requirements_path,
|
||||
)
|
||||
.expect("check system managed configuration");
|
||||
|
||||
assert_eq!((legacy_only, with_system_requirements), (false, true));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_v2_rejects_matching_legacy_profile_in_base_user_config() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user