core: write explicit [projects] tables for trusted projects (#2523)

all of my trust_level settings in my ~/.codex/config.toml were on one
line.
This commit is contained in:
Jeremy Rose
2025-08-21 13:20:36 -07:00
committed by GitHub
parent 5fac7b2566
commit 750ca9e21d

View File

@@ -259,10 +259,53 @@ pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Re
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
// Mark the project as trusted. toml_edit is very good at handling // Ensure we render a human-friendly structure:
// missing properties //
// [projects]
// [projects."/path/to/project"]
// trust_level = "trusted"
//
// rather than inline tables like:
//
// [projects]
// "/path/to/project" = { trust_level = "trusted" }
let project_key = project_path.to_string_lossy().to_string(); let project_key = project_path.to_string_lossy().to_string();
doc["projects"][project_key.as_str()]["trust_level"] = toml_edit::value("trusted");
// Ensure top-level `projects` exists as a non-inline, explicit table. If it
// exists but was previously represented as a non-table (e.g., inline),
// replace it with an explicit table.
{
let root = doc.as_table_mut();
let needs_table = !root.contains_key("projects")
|| root.get("projects").and_then(|i| i.as_table()).is_none();
if needs_table {
root.insert("projects", toml_edit::table());
}
}
let Some(projects_tbl) = doc["projects"].as_table_mut() else {
return Err(anyhow::anyhow!(
"projects table missing after initialization"
));
};
// Ensure the per-project entry is its own explicit table. If it exists but
// is not a table (e.g., an inline table), replace it with an explicit table.
let needs_proj_table = !projects_tbl.contains_key(project_key.as_str())
|| projects_tbl
.get(project_key.as_str())
.and_then(|i| i.as_table())
.is_none();
if needs_proj_table {
projects_tbl.insert(project_key.as_str(), toml_edit::table());
}
let Some(proj_tbl) = projects_tbl
.get_mut(project_key.as_str())
.and_then(|i| i.as_table_mut())
else {
return Err(anyhow::anyhow!("project table missing for {}", project_key));
};
proj_tbl.set_implicit(false);
proj_tbl["trust_level"] = toml_edit::value("trusted");
// ensure codex_home exists // ensure codex_home exists
std::fs::create_dir_all(codex_home)?; std::fs::create_dir_all(codex_home)?;
@@ -1178,4 +1221,96 @@ disable_response_storage = true
Ok(()) Ok(())
} }
#[test]
fn test_set_project_trusted_writes_explicit_tables() -> anyhow::Result<()> {
let codex_home = TempDir::new().unwrap();
let project_dir = TempDir::new().unwrap();
// Call the function under test
set_project_trusted(codex_home.path(), project_dir.path())?;
// Read back the generated config.toml
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
let contents = std::fs::read_to_string(&config_path)?;
// Verify it does not use inline tables for the project entry
assert!(
!contents.contains("{ trust_level"),
"config.toml should not use inline tables:\n{}",
contents
);
// Verify the explicit table for the project exists. toml_edit may choose
// either basic (double-quoted) or literal (single-quoted) strings for keys
// containing backslashes (e.g., on Windows). Accept both forms.
let path_str = project_dir.path().to_string_lossy();
let project_key_double = format!("[projects.\"{}\"]", path_str);
let project_key_single = format!("[projects.'{}']", path_str);
assert!(
contents.contains(&project_key_double) || contents.contains(&project_key_single),
"missing explicit project table header: expected to find `{}` or `{}` in:\n{}",
project_key_double,
project_key_single,
contents
);
// Verify the trust_level entry
assert!(
contents.contains("trust_level = \"trusted\""),
"missing trust_level entry in:\n{}",
contents
);
Ok(())
}
#[test]
fn test_set_project_trusted_converts_inline_to_explicit() -> anyhow::Result<()> {
let codex_home = TempDir::new().unwrap();
let project_dir = TempDir::new().unwrap();
// Seed config.toml with an inline project entry under [projects]
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
let path_str = project_dir.path().to_string_lossy();
// Use a literal-quoted key so Windows backslashes don't require escaping
let initial = format!(
"[projects]\n'{}' = {{ trust_level = \"untrusted\" }}\n",
path_str
);
std::fs::create_dir_all(codex_home.path())?;
std::fs::write(&config_path, initial)?;
// Run the function; it should convert to explicit tables and set trusted
set_project_trusted(codex_home.path(), project_dir.path())?;
let contents = std::fs::read_to_string(&config_path)?;
// Should not contain inline table representation anymore (accept both quote styles)
let inline_double = format!("\"{}\" = {{ trust_level = \"trusted\" }}", path_str);
let inline_single = format!("'{}' = {{ trust_level = \"trusted\" }}", path_str);
assert!(
!contents.contains(&inline_double) && !contents.contains(&inline_single),
"config.toml should not contain inline project table anymore:\n{}",
contents
);
// And explicit child table header for the project
let project_key_double = format!("[projects.\"{}\"]", path_str);
let project_key_single = format!("[projects.'{}']", path_str);
assert!(
contents.contains(&project_key_double) || contents.contains(&project_key_single),
"missing explicit project table header: expected to find `{}` or `{}` in:\n{}",
project_key_double,
project_key_single,
contents
);
// And the trust level value
assert!(contents.contains("trust_level = \"trusted\""));
Ok(())
}
// No test enforcing the presence of a standalone [projects] header.
} }