chore(permissions) Move approved commands to environment_context

This commit is contained in:
Dylan Hurd
2026-05-16 01:56:12 -07:00
parent 326e31ab65
commit 414cad772c
7 changed files with 231 additions and 75 deletions

View File

@@ -14,6 +14,7 @@ pub(crate) struct EnvironmentContext {
pub(crate) timezone: Option<String>,
pub(crate) network: Option<NetworkContext>,
pub(crate) subagents: Option<String>,
pub(crate) approved_command_prefixes: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -130,6 +131,7 @@ impl EnvironmentContext {
timezone,
network,
subagents,
approved_command_prefixes: None,
}
}
@@ -146,6 +148,7 @@ impl EnvironmentContext {
timezone,
network,
subagents,
approved_command_prefixes: None,
}
}
@@ -158,6 +161,7 @@ impl EnvironmentContext {
&& self.timezone == other.timezone
&& self.network == other.network
&& self.subagents == other.subagents
&& self.approved_command_prefixes == other.approved_command_prefixes
}
pub(crate) fn diff_from_turn_context_item(
@@ -232,6 +236,11 @@ impl EnvironmentContext {
self
}
pub(crate) fn with_approved_command_prefixes(mut self, prefixes: Option<String>) -> Self {
self.approved_command_prefixes = prefixes.filter(|prefixes| !prefixes.is_empty());
self
}
fn network_from_turn_context(turn_context: &TurnContext) -> Option<NetworkContext> {
let network = turn_context
.config
@@ -318,6 +327,11 @@ impl ContextualUserFragment for EnvironmentContext {
lines.extend(subagents.lines().map(|line| format!(" {line}")));
lines.push(" </subagents>".to_string());
}
if let Some(prefixes) = &self.approved_command_prefixes {
lines.push(" <approved_command_prefixes>".to_string());
lines.extend(prefixes.lines().map(|line| format!(" {line}")));
lines.push(" </approved_command_prefixes>".to_string());
}
format!("\n{}\n", lines.join("\n"))
}
}

View File

@@ -211,6 +211,37 @@ fn serialize_environment_context_with_subagents() {
assert_eq!(context.render(), expected);
}
#[test]
fn serialize_environment_context_with_approved_command_prefixes() {
let context = EnvironmentContext::new(
vec![EnvironmentContextEnvironment {
id: "local".to_string(),
cwd: test_path_buf("/repo").abs(),
shell: fake_shell_name(),
}],
Some("2026-02-26".to_string()),
Some("America/Los_Angeles".to_string()),
/*network*/ None,
/*subagents*/ None,
)
.with_approved_command_prefixes(Some("- [\"git\", \"pull\"]".to_string()));
let expected = format!(
r#"<environment_context>
<cwd>{}</cwd>
<shell>bash</shell>
<current_date>2026-02-26</current_date>
<timezone>America/Los_Angeles</timezone>
<approved_command_prefixes>
- ["git", "pull"]
</approved_command_prefixes>
</environment_context>"#,
test_path_buf("/repo").display()
);
assert_eq!(context.render(), expected);
}
#[test]
fn serialize_environment_context_with_multiple_selected_environments() {
let local_cwd = test_path_buf("/repo/local");

View File

@@ -1,9 +1,7 @@
use super::ContextualUserFragment;
use codex_execpolicy::Policy;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::format_allow_prefixes;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::GranularApprovalConfig;
@@ -43,10 +41,9 @@ static SANDBOX_MODE_READ_ONLY_TEMPLATE: LazyLock<Template> = LazyLock::new(|| {
.unwrap_or_else(|err| panic!("read-only sandbox template must parse: {err}"))
});
struct PermissionsPromptConfig<'a> {
struct PermissionsPromptConfig {
approval_policy: AskForApproval,
approvals_reviewer: ApprovalsReviewer,
exec_policy: &'a Policy,
exec_permission_approvals_enabled: bool,
request_permissions_tool_enabled: bool,
}
@@ -63,7 +60,6 @@ impl PermissionsInstructions {
permission_profile: &PermissionProfile,
approval_policy: AskForApproval,
approvals_reviewer: ApprovalsReviewer,
exec_policy: &Policy,
cwd: &Path,
exec_permission_approvals_enabled: bool,
request_permissions_tool_enabled: bool,
@@ -76,7 +72,6 @@ impl PermissionsInstructions {
PermissionsPromptConfig {
approval_policy,
approvals_reviewer,
exec_policy,
exec_permission_approvals_enabled,
request_permissions_tool_enabled,
},
@@ -87,7 +82,7 @@ impl PermissionsInstructions {
fn from_permissions_with_network(
sandbox_mode: SandboxMode,
network_access: NetworkAccess,
config: PermissionsPromptConfig<'_>,
config: PermissionsPromptConfig,
writable_roots: Option<Vec<WritableRoot>>,
) -> Self {
let mut text = String::new();
@@ -97,7 +92,6 @@ impl PermissionsInstructions {
&approval_text(
config.approval_policy,
config.approvals_reviewer,
config.exec_policy,
config.exec_permission_approvals_enabled,
config.request_permissions_tool_enabled,
),
@@ -164,7 +158,6 @@ fn append_section(text: &mut String, section: &str) {
fn approval_text(
approval_policy: AskForApproval,
approvals_reviewer: ApprovalsReviewer,
exec_policy: &Policy,
exec_permission_approvals_enabled: bool,
request_permissions_tool_enabled: bool,
) -> String {
@@ -185,11 +178,6 @@ fn approval_text(
if request_permissions_tool_enabled {
sections.push(request_permissions_tool_prompt_section().to_string());
}
if let Some(prefixes) = approved_command_prefixes_text(exec_policy) {
sections.push(format!(
"## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"
));
}
sections.join("\n\n")
};
let text = match approval_policy {
@@ -201,7 +189,6 @@ fn approval_text(
AskForApproval::OnRequest => on_request_instructions(),
AskForApproval::Granular(granular_config) => granular_instructions(
granular_config,
exec_policy,
exec_permission_approvals_enabled,
request_permissions_tool_enabled,
),
@@ -246,11 +233,6 @@ fn writable_roots_text(writable_roots: Option<Vec<WritableRoot>>) -> Option<Stri
})
}
fn approved_command_prefixes_text(exec_policy: &Policy) -> Option<String> {
format_allow_prefixes(exec_policy.get_allowed_prefixes())
.filter(|prefixes| !prefixes.is_empty())
}
fn granular_prompt_intro_text() -> &'static str {
"# Approval Requests\n\nApproval policy is `granular`. Categories set to `false` are automatically rejected instead of prompting the user."
}
@@ -261,7 +243,6 @@ fn request_permissions_tool_prompt_section() -> &'static str {
fn granular_instructions(
granular_config: GranularApprovalConfig,
exec_policy: &Policy,
exec_permission_approvals_enabled: bool,
request_permissions_tool_enabled: bool,
) -> String {
@@ -322,12 +303,6 @@ fn granular_instructions(
sections.push(request_permissions_tool_prompt_section().to_string());
}
if let Some(prefixes) = approved_command_prefixes_text(exec_policy) {
sections.push(format!(
"## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"
));
}
sections.join("\n\n")
}

View File

@@ -1,5 +1,4 @@
use super::*;
use codex_execpolicy::Decision;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -35,7 +34,6 @@ fn builds_permissions_with_network_access_override() {
PermissionsPromptConfig {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: false,
request_permissions_tool_enabled: false,
},
@@ -72,7 +70,6 @@ fn builds_permissions_from_profile() {
&permission_profile,
AskForApproval::UnlessTrusted,
ApprovalsReviewer::User,
&Policy::empty(),
&cwd,
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,
@@ -84,28 +81,16 @@ fn builds_permissions_from_profile() {
}
#[test]
fn includes_request_rule_instructions_for_on_request() {
let mut exec_policy = Policy::empty();
exec_policy
.add_prefix_rule(&["git".to_string(), "pull".to_string()], Decision::Allow)
.expect("add rule");
let instructions = PermissionsInstructions::from_permissions_with_network(
SandboxMode::WorkspaceWrite,
NetworkAccess::Enabled,
PermissionsPromptConfig {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &exec_policy,
exec_permission_approvals_enabled: false,
request_permissions_tool_enabled: false,
},
/*writable_roots*/ None,
fn on_request_approval_text_matches_prompt() {
assert_eq!(
approval_text(
AskForApproval::OnRequest,
ApprovalsReviewer::User,
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,
),
APPROVAL_POLICY_ON_REQUEST_RULE.to_string(),
);
let text = instructions.body();
assert!(text.contains("prefix_rule"));
assert!(text.contains("Approved command prefixes"));
assert!(text.contains(r#"["git", "pull"]"#));
}
#[test]
@@ -116,7 +101,6 @@ fn includes_request_permissions_tool_instructions_for_unless_trusted_when_enable
PermissionsPromptConfig {
approval_policy: AskForApproval::UnlessTrusted,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: false,
request_permissions_tool_enabled: true,
},
@@ -136,7 +120,6 @@ fn includes_request_permissions_tool_instructions_for_on_failure_when_enabled()
PermissionsPromptConfig {
approval_policy: AskForApproval::OnFailure,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: false,
request_permissions_tool_enabled: true,
},
@@ -156,7 +139,6 @@ fn includes_request_permission_rule_instructions_for_on_request_when_enabled() {
PermissionsPromptConfig {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: true,
request_permissions_tool_enabled: false,
},
@@ -176,7 +158,6 @@ fn includes_request_permissions_tool_instructions_for_on_request_when_tool_is_en
PermissionsPromptConfig {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: false,
request_permissions_tool_enabled: true,
},
@@ -196,7 +177,6 @@ fn on_request_includes_tool_guidance_alongside_inline_permission_guidance_when_b
PermissionsPromptConfig {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::User,
exec_policy: &Policy::empty(),
exec_permission_approvals_enabled: true,
request_permissions_tool_enabled: true,
},
@@ -213,7 +193,6 @@ fn auto_review_approvals_append_auto_review_specific_guidance() {
let text = approval_text(
AskForApproval::OnRequest,
ApprovalsReviewer::AutoReview,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,
);
@@ -228,7 +207,6 @@ fn auto_review_approvals_omit_auto_review_specific_guidance_when_approval_is_nev
let text = approval_text(
AskForApproval::Never,
ApprovalsReviewer::AutoReview,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,
);
@@ -280,7 +258,6 @@ fn granular_policy_lists_prompted_and_rejected_categories_separately() {
mcp_elicitations: false,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ true,
/*request_permissions_tool_enabled*/ false,
);
@@ -317,7 +294,6 @@ fn granular_policy_includes_command_permission_instructions_when_sandbox_approva
mcp_elicitations: true,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ true,
/*request_permissions_tool_enabled*/ false,
);
@@ -349,7 +325,6 @@ fn granular_policy_omits_shell_permission_instructions_when_inline_requests_are_
mcp_elicitations: true,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,
);
@@ -381,7 +356,6 @@ fn granular_policy_includes_request_permissions_tool_only_when_that_prompt_can_s
mcp_elicitations: true,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ true,
/*request_permissions_tool_enabled*/ true,
);
@@ -396,7 +370,6 @@ fn granular_policy_includes_request_permissions_tool_only_when_that_prompt_can_s
mcp_elicitations: true,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ true,
/*request_permissions_tool_enabled*/ true,
);
@@ -414,7 +387,6 @@ fn granular_policy_lists_request_permissions_category_without_tool_section_when_
mcp_elicitations: false,
}),
ApprovalsReviewer::User,
&Policy::empty(),
/*exec_permission_approvals_enabled*/ true,
/*request_permissions_tool_enabled*/ false,
);

View File

@@ -10,7 +10,6 @@ use crate::context::RealtimeStartWithInstructions;
use crate::session::PreviousTurnSettings;
use crate::session::turn_context::TurnContext;
use crate::shell::Shell;
use codex_execpolicy::Policy;
use codex_features::Feature;
use codex_protocol::config_types::Personality;
use codex_protocol::models::ContentItem;
@@ -42,7 +41,6 @@ fn build_environment_update_item(
fn build_permissions_update_item(
previous: Option<&TurnContextItem>,
next: &TurnContext,
exec_policy: &Policy,
) -> Option<String> {
if !next.config.include_permissions_instructions {
return None;
@@ -60,7 +58,6 @@ fn build_permissions_update_item(
&next.permission_profile,
next.approval_policy.value(),
next.config.approvals_reviewer,
exec_policy,
#[allow(deprecated)]
&next.cwd,
next.features.enabled(Feature::ExecPermissionApprovals),
@@ -211,7 +208,6 @@ pub(crate) fn build_settings_update_items(
previous_turn_settings: Option<&PreviousTurnSettings>,
next: &TurnContext,
shell: &Shell,
exec_policy: &Policy,
personality_feature_enabled: bool,
) -> Vec<ResponseItem> {
// TODO(ccunningham): build_settings_update_items still does not cover every
@@ -223,7 +219,7 @@ pub(crate) fn build_settings_update_items(
// Keep model-switch instructions first so model-specific guidance is read before
// any other context diffs on this turn.
build_model_instructions_update_item(previous_turn_settings, next),
build_permissions_update_item(previous, next, exec_policy),
build_permissions_update_item(previous, next),
build_collaboration_mode_update_item(previous, next),
build_realtime_update_item(previous, previous_turn_settings, next),
build_personality_update_item(previous, next, personality_feature_enabled),

View File

@@ -1565,13 +1565,11 @@ impl Session {
state.previous_turn_settings()
};
let shell = self.user_shell();
let exec_policy = self.services.exec_policy.current();
crate::context_manager::updates::build_settings_update_items(
reference_context_item,
previous_turn_settings.as_ref(),
current_context,
shell.as_ref(),
exec_policy.as_ref(),
self.features.enabled(Feature::Personality),
)
}
@@ -2667,7 +2665,6 @@ impl Session {
&turn_context.permission_profile,
turn_context.approval_policy.value(),
turn_context.config.approvals_reviewer,
self.services.exec_policy.current().as_ref(),
#[allow(deprecated)]
&turn_context.cwd,
turn_context
@@ -2807,9 +2804,12 @@ impl Session {
.agent_control
.format_environment_context_subagents(self.conversation_id)
.await;
let approved_command_prefixes =
format_allow_prefixes(self.services.exec_policy.current().get_allowed_prefixes());
contextual_user_sections.push(
crate::context::EnvironmentContext::from_turn_context(turn_context, shell.as_ref())
.with_subagents(subagents)
.with_approved_command_prefixes(approved_command_prefixes)
.render(),
);
}

View File

@@ -4,12 +4,14 @@ use codex_core::ForkSnapshot;
use codex_core::config::Constrained;
use codex_core::context::ContextualUserFragment;
use codex_core::context::PermissionsInstructions;
use codex_core::load_exec_policy;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::responses::ResponsesRequest;
@@ -22,7 +24,9 @@ use core_test_support::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::collections::HashSet;
use std::fs;
use tempfile::TempDir;
fn permissions_texts(request: &ResponsesRequest) -> Vec<String> {
@@ -33,6 +37,14 @@ fn permissions_texts(request: &ResponsesRequest) -> Vec<String> {
.collect()
}
fn environment_context_texts(request: &ResponsesRequest) -> Vec<String> {
request
.message_input_texts("user")
.into_iter()
.filter(|text| text.contains("<environment_context>"))
.collect()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn permissions_message_sent_once_on_start() -> Result<()> {
skip_if_no_network!(Ok(()));
@@ -67,6 +79,164 @@ async fn permissions_message_sent_once_on_start() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn approved_command_prefixes_are_sent_in_initial_environment_context_only() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let req1 = mount_sse_once(
&server,
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
)
.await;
let req2 = mount_sse_once(
&server,
sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]),
)
.await;
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
let rules_dir = config.codex_home.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(
rules_dir.join("default.rules"),
r#"prefix_rule(pattern=["git", "pull"], decision="allow")"#,
)
.expect("write policy");
});
let test = builder.build(&server).await?;
let rollout_path = test
.session_configured
.rollout_path
.clone()
.expect("rollout path");
test.codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello 1".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
})
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
test.codex
.submit(Op::UserInput {
environments: None,
items: vec![UserInput::Text {
text: "hello 2".into(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
})
.await?;
wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let first_environment_contexts = environment_context_texts(&req1.single_request());
assert_eq!(first_environment_contexts.len(), 1);
assert!(
first_environment_contexts[0].contains("<approved_command_prefixes>"),
"expected approved prefixes in environment context: {first_environment_contexts:?}"
);
assert!(first_environment_contexts[0].contains(r#"["git", "pull"]"#));
let first_permissions = permissions_texts(&req1.single_request());
assert_eq!(first_permissions.len(), 1);
assert!(
!first_permissions[0].contains("Approved command prefixes"),
"did not expect approved prefixes in permissions message: {first_permissions:?}"
);
let second_environment_contexts = environment_context_texts(&req2.single_request());
assert_eq!(second_environment_contexts, first_environment_contexts);
let rollout_text = fs::read_to_string(rollout_path)?;
for line in rollout_text.lines() {
let item: Value = serde_json::from_str(line)?;
if item.get("type").and_then(Value::as_str) == Some("turn_context") {
let text = item.to_string();
assert!(
!text.contains("approved_command_prefixes"),
"turn_context rollout item should not include approved prefixes: {text}"
);
assert!(
!text.contains(r#"["git","pull"]"#),
"turn_context rollout item should not include approved prefix list: {text}"
);
}
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn approved_command_prefixes_render_with_multiple_environments() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let req = mount_sse_once(
&server,
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
)
.await;
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
let rules_dir = config.codex_home.join("rules");
fs::create_dir_all(&rules_dir).expect("create rules dir");
fs::write(
rules_dir.join("default.rules"),
r#"prefix_rule(pattern=["cargo", "test"], decision="allow")"#,
)
.expect("write policy");
});
let test = builder.build_with_remote_and_local_env(&server).await?;
let local_cwd = AbsolutePathBuf::from_absolute_path(test.cwd.path().to_path_buf())
.expect("test cwd is absolute");
let remote_cwd = AbsolutePathBuf::from_absolute_path(test.cwd.path().join("remote"))
.expect("remote cwd is absolute");
test.submit_turn_with_environments(
"hello multiple environments",
Some(vec![
TurnEnvironmentSelection {
environment_id: LOCAL_ENVIRONMENT_ID.to_string(),
cwd: local_cwd.clone(),
},
TurnEnvironmentSelection {
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
cwd: remote_cwd.clone(),
},
]),
)
.await?;
let environment_contexts = environment_context_texts(&req.single_request());
assert_eq!(environment_contexts.len(), 1);
let environment_context = &environment_contexts[0];
assert!(environment_context.contains("<environments>"));
assert!(environment_context.contains(&format!(r#"<environment id="{LOCAL_ENVIRONMENT_ID}">"#)));
assert!(environment_context.contains(&format!("<cwd>{}</cwd>", local_cwd.to_string_lossy())));
assert!(
environment_context.contains(&format!(r#"<environment id="{REMOTE_ENVIRONMENT_ID}">"#))
);
assert!(environment_context.contains(&format!("<cwd>{}</cwd>", remote_cwd.to_string_lossy())));
assert!(environment_context.contains("<approved_command_prefixes>"));
assert!(environment_context.contains(r#"["cargo", "test"]"#));
assert!(
!environment_context.contains("\n <cwd>"),
"multiple environment context should not render a legacy top-level cwd: {environment_context}"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn permissions_message_added_on_override_change() -> Result<()> {
skip_if_no_network!(Ok(()));
@@ -578,13 +748,11 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
let permissions = permissions_texts(&req.single_request());
let normalize_line_endings = |s: &str| s.replace("\r\n", "\n");
let exec_policy = load_exec_policy(&test.config.config_layer_stack).await?;
let permission_profile = test.config.permissions.effective_permission_profile();
let expected = PermissionsInstructions::from_permission_profile(
&permission_profile,
AskForApproval::OnRequest,
test.config.approvals_reviewer,
&exec_policy,
test.config.cwd.as_path(),
/*exec_permission_approvals_enabled*/ false,
/*request_permissions_tool_enabled*/ false,