diff --git a/codex-rs/core/src/guardian/prompt.rs b/codex-rs/core/src/guardian/prompt.rs index d32be1aab3..d8aa425e9d 100644 --- a/codex-rs/core/src/guardian/prompt.rs +++ b/codex-rs/core/src/guardian/prompt.rs @@ -244,7 +244,7 @@ pub(crate) async fn build_guardian_prompt_items_with_parent_turn( fn parent_turn_denied_reads_context(turn: &TurnContext) -> Option { #[allow(deprecated)] let cwd = &turn.cwd; - let file_system_policy = turn.permission_profile.file_system_sandbox_policy(); + let file_system_policy = turn.file_system_sandbox_policy(); let mut entries = file_system_policy .get_unreadable_roots_with_cwd(cwd) .into_iter() diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index e399541911..b80361a2d1 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -467,7 +467,7 @@ async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Re session.thread_id = fixed_guardian_parent_session_id(); let denied_root = test_path_buf("/repo/private").abs(); let denied_glob = test_path_buf("/repo/private/**").display().to_string(); - turn.permission_profile = PermissionProfile::from_runtime_permissions( + let permission_profile = PermissionProfile::from_runtime_permissions( &FileSystemSandboxPolicy::restricted(vec![ FileSystemSandboxEntry { path: FileSystemPath::Special { @@ -493,6 +493,10 @@ async fn build_guardian_prompt_includes_parent_turn_denied_reads() -> anyhow::Re ]), NetworkSandboxPolicy::Restricted, ); + Arc::make_mut(&mut turn.config) + .permissions + .set_permission_profile(permission_profile) + .expect("test setup should allow updating permission profile"); let session = Arc::new(session); let turn = Arc::new(turn); seed_guardian_parent_history(&session, &turn).await; diff --git a/codex-rs/core/src/mcp_openai_file.rs b/codex-rs/core/src/mcp_openai_file.rs index 1cc2e72a71..601bc0d2d4 100644 --- a/codex-rs/core/src/mcp_openai_file.rs +++ b/codex-rs/core/src/mcp_openai_file.rs @@ -229,7 +229,6 @@ mod tests { fn set_primary_environment_cwd(turn_context: &mut TurnContext, cwd: &Path) { let cwd = AbsolutePathBuf::try_from(cwd).expect("absolute path"); - turn_context.permission_profile = codex_protocol::models::PermissionProfile::Disabled; let TurnEnvironmentState::Ready(primary) = &mut turn_context.environments.environments[0] else { panic!("expected ready primary environment"); diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 987823eeee..9d1bc206b0 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -87,7 +87,7 @@ fn approval_metadata( fn approval_config(turn_context: &TurnContext) -> codex_mcp::McpConfig { let mut config = (*mcp_config_for_test(&turn_context.config)).clone(); - config.permission_profile = turn_context.permission_profile.clone(); + config.permission_profile = turn_context.permission_profile(); config } @@ -2814,7 +2814,10 @@ async fn full_access_mode_skips_mcp_tool_approval_for_all_approval_modes() { .approval_policy .set(AskForApproval::Never) .expect("test setup should allow updating approval policy"); - turn_context.permission_profile = PermissionProfile::Disabled; + Arc::make_mut(&mut turn_context.config) + .permissions + .set_permission_profile(PermissionProfile::Disabled) + .expect("test setup should allow updating permission profile"); let session = Arc::new(session); let turn_context = Arc::new(turn_context); diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 4d9bc11f8d..afea0906c0 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -95,7 +95,7 @@ pub(super) async fn spawn_review_thread( review_turn_id.clone(), #[allow(deprecated)] parent_turn_context.cwd.clone(), - &parent_turn_context.permission_profile, + &parent_turn_context.permission_profile(), parent_turn_context.windows_sandbox_level, parent_turn_context.network.is_some(), )); @@ -134,7 +134,6 @@ pub(super) async fn spawn_review_thread( .clone(), multi_agent_version: MultiAgentVersion::Disabled, personality: parent_turn_context.personality, - permission_profile: parent_turn_context.permission_profile(), network: parent_turn_context.network.clone(), windows_sandbox_level: parent_turn_context.windows_sandbox_level, #[allow(deprecated)] diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 486ccbac91..37348d40a3 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -9246,11 +9246,15 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() { let (_session, mut turn_context) = make_session_and_context().await; let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context); - turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( - turn_context.permission_profile.enforcement(), + let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( + turn_context.permission_profile().enforcement(), &file_system_sandbox_policy, turn_context.network_sandbox_policy(), ); + Arc::make_mut(&mut turn_context.config) + .permissions + .set_permission_profile(permission_profile) + .expect("test setup should allow updating permission profile"); let item = turn_context.to_turn_context_item(); @@ -9409,11 +9413,15 @@ async fn record_context_updates_and_set_reference_context_item_persists_split_fi { let (mut session, mut turn_context) = make_session_and_context().await; let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context); - turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( - turn_context.permission_profile.enforcement(), + let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( + turn_context.permission_profile().enforcement(), &file_system_sandbox_policy, turn_context.network_sandbox_policy(), ); + Arc::make_mut(&mut turn_context.config) + .permissions + .set_permission_profile(permission_profile) + .expect("test setup should allow updating permission profile"); let rollout_path = attach_thread_persistence(&mut session).await; let turn_context = Arc::new(turn_context); @@ -11219,7 +11227,10 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() { // command. Force DangerFullAccess so this check stays focused on approval // policy rather than platform-specific sandbox behavior. let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique thread settings Arc"); - turn_context_mut.permission_profile = PermissionProfile::Disabled; + Arc::make_mut(&mut turn_context_mut.config) + .permissions + .set_permission_profile(PermissionProfile::Disabled) + .expect("test setup should allow updating permission profile"); let command = session.user_shell().derive_exec_args( command_script, diff --git a/codex-rs/core/src/session/tests/guardian_tests.rs b/codex-rs/core/src/session/tests/guardian_tests.rs index 132260023f..9f9e813c4c 100644 --- a/codex-rs/core/src/session/tests/guardian_tests.rs +++ b/codex-rs/core/src/session/tests/guardian_tests.rs @@ -304,7 +304,6 @@ async fn guardian_allows_shell_command_additional_permissions_requests_past_poli .features .enable(Feature::ExecPermissionApprovals) .expect("test setup should allow enabling request permissions"); - turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled; let mut config = (*turn_context_raw.config).clone(); config .permissions @@ -420,7 +419,6 @@ async fn strict_auto_review_turn_grant_forces_guardian_for_shell_command_policy_ .approval_policy .set(AskForApproval::Never) .expect("test setup should allow updating approval policy"); - turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled; let mut config = (*turn_context_raw.config).clone(); config .permissions diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index a3b029cc65..d2055d65f8 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -15,7 +15,6 @@ use codex_protocol::protocol::ErrorEvent; use codex_protocol::protocol::MultiAgentVersion; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::TurnEnvironmentSelection; -use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile; use codex_sandboxing::policy_transforms::effective_permission_profile; use codex_utils_path_uri::PathUri; use futures::FutureExt; @@ -136,7 +135,6 @@ pub struct TurnContext { pub(crate) collaboration_mode_developer_instructions: Option, pub(crate) multi_agent_version: MultiAgentVersion, pub(crate) personality: Option, - pub(crate) permission_profile: PermissionProfile, pub(crate) network: Option, pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) available_models: Vec, @@ -190,23 +188,20 @@ impl TurnContext { } pub(crate) fn permission_profile(&self) -> PermissionProfile { - self.permission_profile.clone() + self.config.permissions.effective_permission_profile() } pub(crate) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { - self.permission_profile.file_system_sandbox_policy() + self.config.permissions.file_system_sandbox_policy() } pub(crate) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy { - self.permission_profile.network_sandbox_policy() + self.config.permissions.network_sandbox_policy() } pub(crate) fn sandbox_policy(&self) -> SandboxPolicy { - compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, - #[allow(deprecated)] - &self.cwd, - ) + #[allow(deprecated)] + self.config.permissions.legacy_sandbox_policy(&self.cwd) } pub(crate) fn effective_reasoning_effort(&self) -> Option { @@ -313,7 +308,6 @@ impl TurnContext { .clone(), multi_agent_version: self.multi_agent_version, personality: self.personality, - permission_profile: self.permission_profile.clone(), network: self.network.clone(), windows_sandbox_level: self.windows_sandbox_level, available_models, @@ -581,7 +575,6 @@ impl Session { .clone(), multi_agent_version, personality: session_configuration.personality, - permission_profile, network, windows_sandbox_level: session_configuration.windows_sandbox_level, available_models, diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 83c40aa2f3..a7d9b6446d 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -2262,12 +2262,20 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() { .expect("approval policy should be set"); let mut config = (*turn.config).clone(); config.approvals_reviewer = ApprovalsReviewer::AutoReview; + config + .permissions + .set_permission_profile(expected_permission_profile.clone()) + .expect("test setup should allow updating permission profile"); set_turn_config(&mut turn, config); - turn.permission_profile = expected_permission_profile.clone(); + let role_name = install_role_with_model_override(&mut turn).await; + let mut role_config = (*turn.config).clone(); + crate::agent::role::apply_role_to_config(&mut role_config, Some(role_name.as_str())) + .await + .expect("non-empty role config should apply"); assert_ne!( + role_config.permissions.effective_permission_profile(), expected_permission_profile, - turn.config.permissions.effective_permission_profile(), - "test requires a runtime profile override that differs from base config" + "role config must discard the runtime permission override before it is reapplied" ); let invocation = invocation( @@ -2276,7 +2284,7 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() { "spawn_agent", function_payload(json!({ "message": "await this command", - "agent_type": "explorer" + "agent_type": role_name })), ); let output = SpawnAgentHandler::default() @@ -4438,7 +4446,10 @@ async fn build_agent_spawn_config_uses_turn_context_values() { &file_system_sandbox_policy, network_sandbox_policy, ); - turn.permission_profile = permission_profile.clone(); + Arc::make_mut(&mut turn.config) + .permissions + .set_permission_profile(permission_profile) + .expect("permission profile set"); Arc::make_mut(&mut turn.config) .permissions .approval_policy @@ -4464,7 +4475,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() { .expect("approval policy set"); expected .permissions - .set_permission_profile(permission_profile) + .set_permission_profile(turn.permission_profile()) .expect("permission profile set"); assert_eq!(config, expected); } diff --git a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs index 723eb93c84..8a446083ed 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs @@ -156,7 +156,7 @@ impl ExecCommandHandler { // permissions config below. Consult the configured platform-sandbox requirement before // deciding whether parsing may continue without that base path. let sandbox = SandboxManager::new().select_initial( - &turn.permission_profile, + &turn.permission_profile(), SandboxablePreference::Auto, turn.windows_sandbox_level, turn.network.is_some(), diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 675f409b72..af253d5573 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -316,7 +316,10 @@ mod tests { replace_primary_environment_cwd(&mut turn, image_cwd.clone()); let image_path = image_cwd.join("image.png"); std::fs::write(image_path.as_path(), b"not a real image").expect("write test image"); - turn.permission_profile = PermissionProfile::read_only(); + Arc::make_mut(&mut turn.config) + .permissions + .set_permission_profile(PermissionProfile::read_only()) + .expect("test setup should allow updating permission profile"); let turn = Arc::new(turn); let result = ViewImageHandler::default() diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index 898523b090..417e622137 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -443,11 +443,12 @@ impl ToolRegistry { let tool_name_flat = flat_tool_name(&tool_name); let call_id_owned = invocation.call_id.clone(); let otel = invocation.turn.session_telemetry.clone(); + let permission_profile = invocation.turn.permission_profile(); let base_tool_result_tags = [ ( "sandbox", permission_profile_sandbox_tag( - &invocation.turn.permission_profile, + &permission_profile, invocation.turn.windows_sandbox_level, invocation.turn.network.is_some(), ), @@ -455,7 +456,7 @@ impl ToolRegistry { ( "sandbox_policy", permission_profile_policy_tag( - &invocation.turn.permission_profile, + &permission_profile, #[allow(deprecated)] invocation.turn.cwd.as_path(), ), diff --git a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs index 4b872afb28..7834ddc7c3 100644 --- a/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs +++ b/codex-rs/core/src/tools/runtimes/shell/unix_escalation_tests.rs @@ -550,10 +550,13 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul Arc::make_mut(&mut turn_context.config) .permissions .approval_policy = Constrained::allow_any(AskForApproval::OnRequest); - turn_context.permission_profile = PermissionProfile::from_runtime_permissions( - &read_only_file_system_sandbox_policy(), - NetworkSandboxPolicy::Restricted, - ); + Arc::make_mut(&mut turn_context.config) + .permissions + .set_permission_profile(PermissionProfile::from_runtime_permissions( + &read_only_file_system_sandbox_policy(), + NetworkSandboxPolicy::Restricted, + )) + .expect("test setup should allow updating permission profile"); let workdir = AbsolutePathBuf::try_from(std::env::current_dir()?)?; let target = std::env::temp_dir().join("execve-hook-short-circuit.txt"); let target_str = target.display().to_string(); diff --git a/codex-rs/core/tests/suite/multi_agent_resume.rs b/codex-rs/core/tests/suite/multi_agent_resume.rs index 7a4c7c738c..e0438e9d24 100644 --- a/codex-rs/core/tests/suite/multi_agent_resume.rs +++ b/codex-rs/core/tests/suite/multi_agent_resume.rs @@ -310,6 +310,7 @@ async fn cold_root_resume_restores_agent_identity_and_role_on_followup() -> Resu assert!(initial_child_request.requests().iter().any(|request| { request.body_contains_text(INITIAL_TASK) && request.body_contains_text(ROLE_DEVELOPER_INSTRUCTIONS) + && request.body_contains_text("") && !request.body_contains_text(SUBAGENT_DEVELOPER_INSTRUCTIONS) })); let initial_worker_config = worker_thread.config_snapshot().await; @@ -492,6 +493,7 @@ async fn cold_root_resume_restores_agent_identity_and_role_on_followup() -> Resu assert!(followup_child_request.requests().iter().any(|request| { request.body_contains_text(FOLLOWUP_TASK) && request.body_contains_text(ROLE_DEVELOPER_INSTRUCTIONS) + && request.body_contains_text("") && !request.body_contains_text(SUBAGENT_DEVELOPER_INSTRUCTIONS) })); let requests = server