diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 2cde43ae..67a54eb1 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -279,12 +279,14 @@ pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Re // 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 mut created_projects_table = false; { 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()); + created_projects_table = true; } } let Some(projects_tbl) = doc["projects"].as_table_mut() else { @@ -293,6 +295,12 @@ pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Re )); }; + // If we created the `projects` table ourselves, keep it implicit so we + // don't render a standalone `[projects]` header. + if created_projects_table { + projects_tbl.set_implicit(true); + } + // 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()) @@ -1254,37 +1262,22 @@ disable_response_storage = true // Call the function under test set_project_trusted(codex_home.path(), project_dir.path())?; - // Read back the generated config.toml + // Read back the generated config.toml and assert exact contents 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 + let raw_path = project_dir.path().to_string_lossy(); + let path_str = if raw_path.contains('\\') { + format!("'{}'", raw_path) + } else { + format!("\"{}\"", raw_path) + }; + let expected = format!( + r#"[projects.{path_str}] +trust_level = "trusted" +"# ); + assert_eq!(contents, expected); Ok(()) } @@ -1296,11 +1289,17 @@ disable_response_storage = true // 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 raw_path = project_dir.path().to_string_lossy(); + let path_str = if raw_path.contains('\\') { + format!("'{}'", raw_path) + } else { + format!("\"{}\"", raw_path) + }; + // Use a quoted key so backslashes don't require escaping on Windows let initial = format!( - "[projects]\n'{}' = {{ trust_level = \"untrusted\" }}\n", - path_str + r#"[projects] +{path_str} = {{ trust_level = "untrusted" }} +"# ); std::fs::create_dir_all(codex_home.path())?; std::fs::write(&config_path, initial)?; @@ -1310,28 +1309,15 @@ disable_response_storage = true 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 - ); + // Assert exact output after conversion to explicit table + let expected = format!( + r#"[projects] - // 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 +[projects.{path_str}] +trust_level = "trusted" +"# ); - - // And the trust level value - assert!(contents.contains("trust_level = \"trusted\"")); + assert_eq!(contents, expected); Ok(()) }