diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 21d291b3099..acd906abbd7 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -1822,6 +1822,8 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl ) .await?; + assert!(config.explicit_permission_profile_mode); + assert!(config.custom_permission_profile_ids.is_empty()); let policy = config.permissions.file_system_sandbox_policy(); assert_eq!( config @@ -2377,6 +2379,10 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root() ) .await?; + assert_eq!( + config.custom_permission_profile_ids, + vec!["workspace".to_string()] + ); let memories_root = AbsolutePathBuf::from_absolute_path(std::fs::canonicalize( codex_home.path().join("memories"), )?)?; @@ -7664,6 +7670,8 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { windows_sandbox_mode: None, windows_sandbox_private_desktop: true, }, + explicit_permission_profile_mode: false, + custom_permission_profile_ids: Vec::new(), approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, @@ -8115,6 +8123,8 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { windows_sandbox_mode: None, windows_sandbox_private_desktop: true, }, + explicit_permission_profile_mode: false, + custom_permission_profile_ids: Vec::new(), approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, @@ -8280,6 +8290,8 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { windows_sandbox_mode: None, windows_sandbox_private_desktop: true, }, + explicit_permission_profile_mode: false, + custom_permission_profile_ids: Vec::new(), approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, @@ -8430,6 +8442,8 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { windows_sandbox_mode: None, windows_sandbox_private_desktop: true, }, + explicit_permission_profile_mode: false, + custom_permission_profile_ids: Vec::new(), approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(/*initial_value*/ None), user_instructions: None, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 71a55b98dc4..30e653e3253 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -538,6 +538,13 @@ pub struct Config { /// Effective permission configuration for shell tool execution. pub permissions: Permissions, + /// Whether config explicitly selected named permissions profiles instead + /// of the legacy `sandbox_mode` syntax. + pub explicit_permission_profile_mode: bool, + + /// User-defined permission profile IDs available from effective config. + pub custom_permission_profile_ids: Vec, + /// Configures who approval requests are routed to for review once they have /// been escalated. This does not disable separate safety checks such as /// ARC. @@ -2594,6 +2601,17 @@ impl Config { Some(PermissionConfigSyntax::Profiles) ) || permission_config_syntax.is_none(); + let explicit_permission_profile_mode = default_permissions_override.is_some() + || matches!( + permission_config_syntax, + Some(PermissionConfigSyntax::Profiles) + ); + let custom_permission_profile_ids = cfg + .permissions + .as_ref() + .map_or_else(Vec::new, |permissions| { + permissions.entries.keys().cloned().collect() + }); let using_implicit_builtin_profile = permission_config_syntax.is_none() && default_permissions.is_none(); let should_seed_legacy_workspace_roots = default_permissions.is_none() @@ -3361,6 +3379,8 @@ impl Config { windows_sandbox_mode, windows_sandbox_private_desktop, }, + explicit_permission_profile_mode, + custom_permission_profile_ids, approvals_reviewer: constrained_approvals_reviewer.value(), enforce_residency: enforce_residency.value, notify: cfg.notify, diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 8a168bad138..434b58259f6 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -175,6 +175,8 @@ fn new_config(model: Option, arg0_paths: Arg0DispatchPaths) -> anyhow::R Constrained::allow_any(AskForApproval::Never), Constrained::allow_any(PermissionProfile::read_only()), )?, + explicit_permission_profile_mode: false, + custom_permission_profile_ids: Vec::new(), approvals_reviewer: ApprovalsReviewer::User, enforce_residency: Constrained::allow_any(/*initial_value*/ None), hide_agent_reasoning: false,