fix: downgrade managed requirement policy rejects to warnings

This commit is contained in:
daniel-oai
2026-02-17 14:05:33 -08:00
parent 247e470e02
commit d46244a852
2 changed files with 149 additions and 15 deletions

View File

@@ -3265,6 +3265,8 @@ mod handlers {
use codex_protocol::protocol::WarningEvent;
use codex_protocol::request_user_input::RequestUserInputResponse;
use crate::config::ConstraintError;
use crate::config_loader::RequirementSource;
use crate::context_manager::is_user_turn_boundary;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
@@ -3287,12 +3289,37 @@ mod handlers {
sess.close_unified_exec_processes().await;
}
fn disallowed_setting_update_warning(err: &ConstraintError) -> Option<String> {
match err {
ConstraintError::InvalidValue {
field_name,
requirement_source,
..
} if matches!(*field_name, "approval_policy" | "sandbox_mode")
&& !matches!(requirement_source, RequirementSource::Unknown) =>
{
Some(format!(
"Requested `{field_name}` is disallowed by requirements; continuing with the current value. Details: {err}"
))
}
_ => None,
}
}
pub async fn override_turn_context(
sess: &Session,
sub_id: String,
updates: SessionSettingsUpdate,
) {
if let Err(err) = sess.update_settings(updates).await {
if let Some(message) = disallowed_setting_update_warning(&err) {
sess.send_event_raw(Event {
id: sub_id,
msg: EventMsg::Warning(WarningEvent { message }),
})
.await;
return;
}
sess.send_event_raw(Event {
id: sub_id,
msg: EventMsg::Error(ErrorEvent {
@@ -6802,6 +6829,61 @@ mod tests {
);
}
#[tokio::test]
async fn override_turn_context_disallowed_sandbox_update_emits_warning() {
let (sess, _tc, rx) = make_session_and_context_with_rx().await;
{
let mut state = sess.state.lock().await;
state.session_configuration.sandbox_policy =
Constrained::new(SandboxPolicy::new_read_only_policy(), |candidate| {
if matches!(candidate, SandboxPolicy::ReadOnly { .. }) {
Ok(())
} else {
Err(crate::config::ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "WorkspaceWrite".to_string(),
allowed: "[ReadOnly]".to_string(),
requirement_source: RequirementSource::CloudRequirements,
})
}
})
.expect("set constrained sandbox policy");
}
handlers::override_turn_context(
sess.as_ref(),
"sub-override".to_string(),
SessionSettingsUpdate {
sandbox_policy: Some(SandboxPolicy::new_workspace_write_policy()),
..Default::default()
},
)
.await;
let evt = tokio::time::timeout(Duration::from_secs(2), rx.recv())
.await
.expect("timeout waiting for event")
.expect("event");
match evt.msg {
EventMsg::Warning(event) => {
assert!(
event
.message
.contains("Requested `sandbox_mode` is disallowed")
);
assert!(event.message.contains("continuing with the current value"));
}
other => panic!("unexpected event: {other:?}"),
}
assert!(rx.try_recv().is_err());
let state = sess.state.lock().await;
assert_eq!(
*state.session_configuration.sandbox_policy.get(),
SandboxPolicy::new_read_only_policy()
);
}
#[tokio::test]
async fn set_rate_limits_updates_plan_type_when_present() {
let codex_home = tempfile::tempdir().expect("create temp dir");

View File

@@ -38,9 +38,11 @@ use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConstraintError;
use codex_core::config::edit::ConfigEdit;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config_loader::ConfigLayerStackOrdering;
use codex_core::config_loader::RequirementSource;
use codex_core::features::Feature;
use codex_core::models_manager::manager::RefreshStrategy;
use codex_core::models_manager::model_presets::HIDE_GPT_5_1_CODEX_MAX_MIGRATION_PROMPT_CONFIG;
@@ -230,6 +232,16 @@ fn emit_project_config_warnings(app_event_tx: &AppEventSender, config: &Config)
)));
}
fn is_managed_requirement_constraint_error(err: &ConstraintError) -> bool {
matches!(
err,
ConstraintError::InvalidValue {
requirement_source,
..
} if !matches!(requirement_source, RequirementSource::Unknown)
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SessionSummary {
usage_line: String,
@@ -651,21 +663,31 @@ impl App {
}
fn apply_runtime_policy_overrides(&mut self, config: &mut Config) {
if let Some(policy) = self.runtime_approval_policy_override.as_ref()
&& let Err(err) = config.permissions.approval_policy.set(*policy)
if let Some(policy) = self.runtime_approval_policy_override
&& let Err(err) = config.permissions.approval_policy.set(policy)
{
tracing::warn!(%err, "failed to carry forward approval policy override");
self.chat_widget.add_error_message(format!(
"Failed to carry forward approval policy override: {err}"
));
if is_managed_requirement_constraint_error(&err) {
self.runtime_approval_policy_override =
Some(config.permissions.approval_policy.value());
} else {
self.chat_widget.add_error_message(format!(
"Failed to carry forward approval policy override: {err}"
));
}
}
if let Some(policy) = self.runtime_sandbox_policy_override.as_ref()
&& let Err(err) = config.permissions.sandbox_policy.set(policy.clone())
if let Some(policy) = self.runtime_sandbox_policy_override.clone()
&& let Err(err) = config.permissions.sandbox_policy.set(policy)
{
tracing::warn!(%err, "failed to carry forward sandbox policy override");
self.chat_widget.add_error_message(format!(
"Failed to carry forward sandbox policy override: {err}"
));
if is_managed_requirement_constraint_error(&err) {
self.runtime_sandbox_policy_override =
Some(config.permissions.sandbox_policy.get().clone());
} else {
self.chat_widget.add_error_message(format!(
"Failed to carry forward sandbox policy override: {err}"
));
}
}
}
@@ -2156,13 +2178,17 @@ impl App {
}
}
AppEvent::UpdateAskForApprovalPolicy(policy) => {
self.runtime_approval_policy_override = Some(policy);
if let Err(err) = self.config.permissions.approval_policy.set(policy) {
tracing::warn!(%err, "failed to set approval policy on app config");
self.chat_widget
.add_error_message(format!("Failed to set approval policy: {err}"));
self.runtime_approval_policy_override =
Some(self.config.permissions.approval_policy.value());
if !is_managed_requirement_constraint_error(&err) {
self.chat_widget
.add_error_message(format!("Failed to set approval policy: {err}"));
}
return Ok(AppRunControl::Continue);
}
self.runtime_approval_policy_override = Some(policy);
self.chat_widget.set_approval_policy(policy);
}
AppEvent::UpdateSandboxPolicy(policy) => {
@@ -2176,8 +2202,12 @@ impl App {
if let Err(err) = self.config.permissions.sandbox_policy.set(policy) {
tracing::warn!(%err, "failed to set sandbox policy on app config");
self.chat_widget
.add_error_message(format!("Failed to set sandbox policy: {err}"));
self.runtime_sandbox_policy_override =
Some(self.config.permissions.sandbox_policy.get().clone());
if !is_managed_requirement_constraint_error(&err) {
self.chat_widget
.add_error_message(format!("Failed to set sandbox policy: {err}"));
}
return Ok(AppRunControl::Continue);
}
if let Err(err) = self.chat_widget.set_sandbox_policy(policy_for_chat) {
@@ -2972,6 +3002,28 @@ mod tests {
);
}
#[test]
fn managed_requirement_constraint_error_detects_invalid_value_with_known_source() {
let err = ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "WorkspaceWrite".to_string(),
allowed: "[ReadOnly]".to_string(),
requirement_source: RequirementSource::CloudRequirements,
};
assert!(is_managed_requirement_constraint_error(&err));
}
#[test]
fn managed_requirement_constraint_error_ignores_unknown_source() {
let err = ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "WorkspaceWrite".to_string(),
allowed: "[ReadOnly]".to_string(),
requirement_source: RequirementSource::Unknown,
};
assert!(!is_managed_requirement_constraint_error(&err));
}
#[test]
fn startup_waiting_gate_holds_active_thread_events_until_primary_thread_configured() {
let mut wait_for_initial_session =