mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Warn on malformed disabled project config
This commit is contained in:
@@ -906,6 +906,13 @@ fn project_ignored_config_keys_warning(
|
||||
)
|
||||
}
|
||||
|
||||
fn malformed_disabled_project_config_warning(config_path: &Path, err: &toml::de::Error) -> String {
|
||||
format!(
|
||||
"Ignored malformed project-local config in {} because this project is not trusted: {err}",
|
||||
config_path.display()
|
||||
)
|
||||
}
|
||||
|
||||
async fn project_trust_context(
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
merged_config: &TomlValue,
|
||||
@@ -1182,6 +1189,10 @@ async fn load_project_layers(
|
||||
),
|
||||
));
|
||||
}
|
||||
startup_warnings.push(malformed_disabled_project_config_warning(
|
||||
config_file.as_path(),
|
||||
&e,
|
||||
));
|
||||
layers.push(project_layer_entry(
|
||||
&dot_codex_abs,
|
||||
TomlValue::Table(toml::map::Map::new()),
|
||||
@@ -1208,6 +1219,7 @@ async fn load_project_layers(
|
||||
config,
|
||||
hooks_config_folder_override.as_ref(),
|
||||
decision.is_trusted(),
|
||||
&mut startup_warnings,
|
||||
)
|
||||
.await?;
|
||||
if disabled_reason.is_none() && !ignored_project_config_keys.is_empty() {
|
||||
@@ -1234,6 +1246,7 @@ async fn load_project_layers(
|
||||
TomlValue::Table(toml::map::Map::new()),
|
||||
hooks_config_folder_override.as_ref(),
|
||||
decision.is_trusted(),
|
||||
&mut startup_warnings,
|
||||
)
|
||||
.await?;
|
||||
layers.push(project_layer_entry(
|
||||
@@ -1266,6 +1279,7 @@ async fn merge_root_checkout_project_hooks(
|
||||
mut config: TomlValue,
|
||||
hooks_config_folder_override: Option<&AbsolutePathBuf>,
|
||||
is_trusted: bool,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) -> io::Result<TomlValue> {
|
||||
let Some(hooks_config_folder) = hooks_config_folder_override else {
|
||||
return Ok(config);
|
||||
@@ -1288,6 +1302,10 @@ async fn merge_root_checkout_project_hooks(
|
||||
),
|
||||
));
|
||||
}
|
||||
startup_warnings.push(malformed_disabled_project_config_warning(
|
||||
hooks_config_file.as_path(),
|
||||
&err,
|
||||
));
|
||||
TomlValue::Table(toml::map::Map::new())
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1830,6 +1830,76 @@ async fn linked_worktree_project_layers_use_root_repo_hooks_without_worktree_con
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn malformed_linked_worktree_root_hooks_config_warns_when_project_is_disabled()
|
||||
-> std::io::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let repo_root = tmp.path().join("repo");
|
||||
let root_dot_codex = repo_root.join(".codex");
|
||||
let worktree_root = tmp.path().join("worktree");
|
||||
|
||||
tokio::fs::create_dir_all(&root_dot_codex).await?;
|
||||
tokio::fs::create_dir_all(worktree_root.join(".codex")).await?;
|
||||
write_linked_worktree_pointer(&repo_root, &worktree_root).await?;
|
||||
let root_config_path = root_dot_codex.join(CONFIG_TOML_FILE);
|
||||
tokio::fs::write(
|
||||
&root_config_path,
|
||||
r#"[hooks
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let codex_home = tmp.path().join("home");
|
||||
tokio::fs::create_dir_all(&codex_home).await?;
|
||||
make_config_for_test(
|
||||
&codex_home,
|
||||
&repo_root,
|
||||
TrustLevel::Untrusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&worktree_root)?;
|
||||
let layers = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
&codex_home,
|
||||
Some(cwd),
|
||||
&[] as &[(String, TomlValue)],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let project_layers: Vec<_> = layers
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
|
||||
.collect();
|
||||
assert_eq!(project_layers.len(), 1);
|
||||
assert!(
|
||||
project_layers[0].disabled_reason.is_some(),
|
||||
"expected malformed linked worktree root hooks config layer to be disabled"
|
||||
);
|
||||
assert_eq!(project_hook_command(project_layers[0]), None);
|
||||
let warnings = layers
|
||||
.startup_warnings()
|
||||
.expect("expected malformed linked worktree root hooks config warning");
|
||||
assert_eq!(warnings.len(), 1);
|
||||
assert!(
|
||||
warnings[0].contains(&format!(
|
||||
"Ignored malformed project-local config in {} because this project is not trusted:",
|
||||
root_config_path.display()
|
||||
)),
|
||||
"warning should explain that malformed linked worktree hooks config was ignored"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nested_project_root_markers_do_not_redirect_regular_repo_hooks() -> std::io::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
@@ -2328,6 +2398,170 @@ profile = "ignored"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn malformed_project_config_warns_when_disabled_and_fails_when_trusted() -> std::io::Result<()>
|
||||
{
|
||||
let tmp = tempdir()?;
|
||||
let project_root = tmp.path().join("project");
|
||||
let nested = project_root.join("child");
|
||||
let dot_codex = nested.join(".codex");
|
||||
tokio::fs::create_dir_all(&dot_codex).await?;
|
||||
let project_config_path = dot_codex.join(CONFIG_TOML_FILE);
|
||||
tokio::fs::write(
|
||||
&project_config_path,
|
||||
r#"foo = "child
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(&nested)?;
|
||||
|
||||
let codex_home_untrusted = tmp.path().join("home_untrusted_malformed");
|
||||
tokio::fs::create_dir_all(&codex_home_untrusted).await?;
|
||||
make_config_for_test(
|
||||
&codex_home_untrusted,
|
||||
&project_root,
|
||||
TrustLevel::Untrusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
let untrusted_config_path = codex_home_untrusted.join(CONFIG_TOML_FILE);
|
||||
let untrusted_config_contents = tokio::fs::read_to_string(&untrusted_config_path).await?;
|
||||
tokio::fs::write(
|
||||
&untrusted_config_path,
|
||||
format!(
|
||||
r#"foo = "user"
|
||||
{untrusted_config_contents}"#
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let layers_untrusted = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
&codex_home_untrusted,
|
||||
Some(cwd.clone()),
|
||||
&[] as &[(String, TomlValue)],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
.await?;
|
||||
let project_layers_untrusted: Vec<_> = layers_untrusted
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
|
||||
.collect();
|
||||
assert_eq!(project_layers_untrusted.len(), 1);
|
||||
assert!(
|
||||
project_layers_untrusted[0].disabled_reason.is_some(),
|
||||
"expected malformed untrusted project layer to be disabled"
|
||||
);
|
||||
assert_eq!(
|
||||
project_layers_untrusted[0].config,
|
||||
TomlValue::Table(toml::map::Map::new())
|
||||
);
|
||||
assert_eq!(
|
||||
layers_untrusted.effective_config().get("foo"),
|
||||
Some(&TomlValue::String("user".to_string()))
|
||||
);
|
||||
let untrusted_warnings = layers_untrusted
|
||||
.startup_warnings()
|
||||
.expect("expected malformed project config warning");
|
||||
assert_eq!(untrusted_warnings.len(), 1);
|
||||
assert!(
|
||||
untrusted_warnings[0].contains(&format!(
|
||||
"Ignored malformed project-local config in {} because this project is not trusted:",
|
||||
project_config_path.display()
|
||||
)),
|
||||
"warning should explain that malformed untrusted project config was ignored"
|
||||
);
|
||||
|
||||
let codex_home_unknown = tmp.path().join("home_unknown_malformed");
|
||||
tokio::fs::create_dir_all(&codex_home_unknown).await?;
|
||||
tokio::fs::write(
|
||||
codex_home_unknown.join(CONFIG_TOML_FILE),
|
||||
r#"foo = "user"
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let layers_unknown = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
&codex_home_unknown,
|
||||
Some(cwd.clone()),
|
||||
&[] as &[(String, TomlValue)],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
.await?;
|
||||
let project_layers_unknown: Vec<_> = layers_unknown
|
||||
.get_layers(
|
||||
ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|layer| matches!(layer.name, ConfigLayerSource::Project { .. }))
|
||||
.collect();
|
||||
assert_eq!(project_layers_unknown.len(), 1);
|
||||
assert!(
|
||||
project_layers_unknown[0].disabled_reason.is_some(),
|
||||
"expected malformed unknown-trust project layer to be disabled"
|
||||
);
|
||||
assert_eq!(
|
||||
project_layers_unknown[0].config,
|
||||
TomlValue::Table(toml::map::Map::new())
|
||||
);
|
||||
assert_eq!(
|
||||
layers_unknown.effective_config().get("foo"),
|
||||
Some(&TomlValue::String("user".to_string()))
|
||||
);
|
||||
let unknown_warnings = layers_unknown
|
||||
.startup_warnings()
|
||||
.expect("expected malformed project config warning");
|
||||
assert_eq!(unknown_warnings.len(), 1);
|
||||
assert!(
|
||||
unknown_warnings[0].contains(&format!(
|
||||
"Ignored malformed project-local config in {} because this project is not trusted:",
|
||||
project_config_path.display()
|
||||
)),
|
||||
"warning should explain that malformed unknown-trust project config was ignored"
|
||||
);
|
||||
|
||||
let codex_home_trusted = tmp.path().join("home_trusted_malformed");
|
||||
tokio::fs::create_dir_all(&codex_home_trusted).await?;
|
||||
make_config_for_test(
|
||||
&codex_home_trusted,
|
||||
&project_root,
|
||||
TrustLevel::Trusted,
|
||||
/*project_root_markers*/ None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let err = load_config_layers_state(
|
||||
LOCAL_FS.as_ref(),
|
||||
&codex_home_trusted,
|
||||
Some(cwd),
|
||||
&[] as &[(String, TomlValue)],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
&codex_config::NoopThreadConfigLoader,
|
||||
)
|
||||
.await
|
||||
.expect_err("trusted malformed project config should fail");
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("Error parsing project config file"),
|
||||
"trusted project config parse errors should still fail loudly"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn project_layer_ignores_unsupported_config_keys() -> std::io::Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
|
||||
Reference in New Issue
Block a user