tui: sync session permission profiles (#18284)

## Why

Once `SessionConfigured` carries the active `PermissionProfile`, the TUI
must treat that as authoritative session state. Otherwise the widget can
keep stale local permission details after a session is configured or
resumed.

The TUI also keeps a local `Config` copy used for later operations, so
session-sourced profiles and subsequent local sandbox changes need to
keep the derived split runtime permissions in sync. Because this PR may
land before the follow-up user-turn profile plumbing, embedded
app-server turns also need a standalone path for carrying local runtime
sandbox overrides.

## What changed

- Sync the chat widget runtime filesystem/network permissions from
`SessionConfigured.permission_profile`, with the legacy `sandbox_policy`
as the fallback.
- Recompute split runtime permissions whenever the TUI applies or
carries forward a local sandbox-policy override.
- Mark feature-driven Auto-review sandbox changes as runtime sandbox
overrides so the standalone embedded turn-start profile path is used
even without the follow-up user-turn profile PR.
- Send a turn-start `permissionProfile` for embedded,
non-ExternalSandbox turns when the TUI has a runtime sandbox override;
remote and ExternalSandbox turns keep using the legacy sandbox field.
- Extend coverage for profile sync, local sandbox changes,
ExternalSandbox fallback, feature-driven sandbox overrides, and
turn-start permission override selection.

## Verification

- `cargo test -p codex-tui
update_feature_flags_enabling_guardian_selects_auto_review`
- `cargo test -p codex-tui
turn_start_permission_overrides_send_profiles_only_for_embedded_runtime_overrides`
- `cargo test -p codex-tui permission_settings_sync`
- `cargo test -p codex-tui
session_configured_external_sandbox_keeps_external_runtime_policy`
- `cargo test -p codex-tui
session_configured_syncs_widget_config_permissions_and_cwd`
- `just fix -p codex-tui`



---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18284).
* #18288
* #18287
* #18286
* #18285
* __->__ #18284
This commit is contained in:
Michael Bolin authored and GitHub committed 2026-04-23 09:47:53 -07:00
1 parent 1fda843fbc
commit 5c239ad748
7 files changed
+326 -22

No files matched your search

@@ -1,4 +1,12 @@
use super::*;
use codex_protocol::protocol::FileSystemAccessMode;
use codex_protocol::protocol::FileSystemPath;
use codex_protocol::protocol::FileSystemSandboxEntry;
use codex_protocol::protocol::FileSystemSandboxKind;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::FileSystemSpecialPath;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::NetworkSandboxPolicy;
use pretty_assertions::assert_eq;
#[tokio::test]
@@ -252,6 +260,25 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
let expected_sandbox = SandboxPolicy::new_read_only_policy();
let expected_cwd = test_path_buf("/home/user/sub-agent").abs();
let expected_file_system_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
pattern: "**/.secret".to_string(),
},
access: FileSystemAccessMode::None,
},
]);
let expected_permission_profile =
codex_protocol::models::PermissionProfile::from_runtime_permissions(
&expected_file_system_policy,
NetworkSandboxPolicy::Restricted,
);
let configured = codex_protocol::protocol::SessionConfiguredEvent {
session_id: ThreadId::new(),
forked_from_id: None,
@@ -262,7 +289,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: expected_sandbox.clone(),
permission_profile: None,
permission_profile: Some(expected_permission_profile.clone()),
cwd: expected_cwd.clone(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -285,7 +312,72 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
chat.config_ref().permissions.sandbox_policy.get(),
&expected_sandbox
);
assert_eq!(
chat.config_ref().permissions.permission_profile(),
expected_permission_profile
);
assert_eq!(&chat.config_ref().cwd, &expected_cwd);
let updated_sandbox = SandboxPolicy::new_workspace_write_policy();
chat.set_sandbox_policy(updated_sandbox.clone())
.expect("set sandbox policy");
assert_eq!(
chat.config_ref().permissions.permission_profile(),
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&updated_sandbox,
&expected_cwd
),
"local sandbox changes should replace SessionConfigured profile-derived runtime permissions"
);
}
#[tokio::test]
async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
let (mut chat, _rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await;
let expected_sandbox = SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
};
let configured = codex_protocol::protocol::SessionConfiguredEvent {
session_id: ThreadId::new(),
forked_from_id: None,
thread_name: None,
model: "test-model".to_string(),
model_provider_id: "test-provider".to_string(),
service_tier: None,
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: expected_sandbox.clone(),
permission_profile: None,
cwd: test_path_buf("/home/user/external").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
history_entry_count: 0,
initial_messages: None,
network_proxy: None,
rollout_path: None,
};
chat.handle_codex_event(Event {
id: "session-configured".into(),
msg: EventMsg::SessionConfigured(configured),
});
assert_eq!(
chat.config_ref().permissions.sandbox_policy.get(),
&expected_sandbox
);
assert_eq!(
chat.config_ref()
.permissions
.file_system_sandbox_policy
.kind,
FileSystemSandboxKind::ExternalSandbox,
);
assert_eq!(
chat.config_ref().permissions.network_sandbox_policy,
NetworkSandboxPolicy::Restricted,
);
}
#[tokio::test]