From d19359b31416ccac07f3eff658891fe0d0f1a55e Mon Sep 17 00:00:00 2001 From: Abhinav Vedmala Date: Wed, 27 May 2026 17:20:47 -0700 Subject: [PATCH] Warn on legacy remote sandbox requirements --- codex-rs/config/src/config_requirements.rs | 87 ++++++++++++++++++++-- codex-rs/config/src/lib.rs | 2 + codex-rs/core/src/session/session.rs | 9 +++ 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index b28b0470e3..8e37a3175a 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -770,12 +770,27 @@ pub struct ConfigRequirementsToml { pub guardian_policy_config: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConfigDeprecationNotice { + pub summary: String, + pub details: Option, +} + +const LEGACY_REMOTE_SANDBOX_CONFIG_SUMMARY: &str = + "`[[remote_sandbox_config]]` requirements are deprecated"; +const LEGACY_REMOTE_SANDBOX_CONFIG_DETAILS: &str = "Use keyed `[remote_sandbox_config.]` tables instead. Keep the same `hostname_patterns` and `allowed_sandbox_modes` fields under each named table."; + #[derive(Debug, Clone, Default, PartialEq)] pub struct RemoteSandboxConfigsToml { pub entries: Vec, + legacy_array_shape: bool, } impl RemoteSandboxConfigsToml { + fn uses_legacy_array_shape(&self) -> bool { + self.legacy_array_shape + } + fn matching_allowed_sandbox_modes( &self, hostname: &str, @@ -795,7 +810,10 @@ impl RemoteSandboxConfigsToml { impl From> for RemoteSandboxConfigsToml { fn from(entries: Vec) -> Self { - Self { entries } + Self { + entries, + legacy_array_shape: false, + } } } @@ -811,6 +829,7 @@ impl<'de> Deserialize<'de> for RemoteSandboxConfigsToml { .map(RemoteSandboxConfigToml::deserialize) .collect::, _>>() .map_err(D::Error::custom)?, + legacy_array_shape: true, }), TomlValue::Table(entries) => { let entries = entries @@ -818,7 +837,10 @@ impl<'de> Deserialize<'de> for RemoteSandboxConfigsToml { .map(|(_, entry)| RemoteSandboxConfigToml::deserialize(entry)) .collect::, _>>() .map_err(D::Error::custom)?; - Ok(Self { entries }) + Ok(Self { + entries, + legacy_array_shape: false, + }) } value => Err(D::Error::custom(format!( "expected array or table for remote_sandbox_config, got {value:?}" @@ -1051,6 +1073,21 @@ pub enum ResidencyRequirement { } impl ConfigRequirementsToml { + pub fn deprecation_notices(&self) -> Vec { + let mut notices = Vec::new(); + if self + .remote_sandbox_config + .as_ref() + .is_some_and(RemoteSandboxConfigsToml::uses_legacy_array_shape) + { + notices.push(ConfigDeprecationNotice { + summary: LEGACY_REMOTE_SANDBOX_CONFIG_SUMMARY.to_string(), + details: Some(LEGACY_REMOTE_SANDBOX_CONFIG_DETAILS.to_string()), + }); + } + notices + } + pub fn apply_remote_sandbox_config(&mut self, hostname: Option<&str>) { let Some(remote_sandbox_config) = self.remote_sandbox_config.as_ref() else { return; @@ -2516,16 +2553,16 @@ allowed_approvals_reviewers = ["user"] assert_eq!( config.remote_sandbox_config, - Some( - vec![RemoteSandboxConfigToml { + Some(RemoteSandboxConfigsToml { + entries: vec![RemoteSandboxConfigToml { hostname_patterns: vec!["*.org".to_string(), "runner-??.ci".to_string()], allowed_sandbox_modes: vec![ SandboxModeRequirement::ReadOnly, SandboxModeRequirement::WorkspaceWrite, ], - }] - .into() - ) + }], + legacy_array_shape: true, + }) ); let err = from_str::( @@ -2590,6 +2627,42 @@ allowed_approvals_reviewers = ["user"] Ok(()) } + #[test] + fn remote_sandbox_config_legacy_array_adds_deprecation_notice() -> Result<()> { + let config: ConfigRequirementsToml = from_str( + r#" + [[remote_sandbox_config]] + hostname_patterns = ["*.org"] + allowed_sandbox_modes = ["read-only"] + "#, + )?; + + assert_eq!( + config.deprecation_notices(), + vec![ConfigDeprecationNotice { + summary: LEGACY_REMOTE_SANDBOX_CONFIG_SUMMARY.to_string(), + details: Some(LEGACY_REMOTE_SANDBOX_CONFIG_DETAILS.to_string()), + }] + ); + + Ok(()) + } + + #[test] + fn remote_sandbox_config_keyed_map_has_no_deprecation_notice() -> Result<()> { + let config: ConfigRequirementsToml = from_str( + r#" + [remote_sandbox_config.devboxes] + hostname_patterns = ["*.org"] + allowed_sandbox_modes = ["read-only"] + "#, + )?; + + assert_eq!(config.deprecation_notices(), Vec::new()); + + Ok(()) + } + #[test] fn remote_sandbox_config_matching_entries_intersect_and_override_top_level() -> Result<()> { let source = RequirementSource::CloudRequirements; diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index 5ae2688036..fdec6c2b8d 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -40,6 +40,7 @@ pub use config_requirements::AppToolRequirementToml; pub use config_requirements::AppToolsRequirementsToml; pub use config_requirements::AppsRequirementsToml; pub use config_requirements::ComputerUseRequirementsToml; +pub use config_requirements::ConfigDeprecationNotice; pub use config_requirements::ConfigRequirements; pub use config_requirements::ConfigRequirementsToml; pub use config_requirements::ConfigRequirementsWithSources; @@ -57,6 +58,7 @@ pub use config_requirements::NetworkUnixSocketPermissionToml; pub use config_requirements::NetworkUnixSocketPermissionsToml; pub use config_requirements::PluginRequirementsToml; pub use config_requirements::RemoteSandboxConfigToml; +pub use config_requirements::RemoteSandboxConfigsToml; pub use config_requirements::RequirementSource; pub use config_requirements::ResidencyRequirement; pub use config_requirements::SandboxModeRequirement; diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 0823e93666..0392060d22 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -726,6 +726,15 @@ impl Session { }), }); } + for notice in config.config_layer_stack.requirements_toml().deprecation_notices() { + post_session_configured_events.push(Event { + id: INITIAL_SUBMIT_ID.to_owned(), + msg: EventMsg::DeprecationNotice(DeprecationNoticeEvent { + summary: notice.summary, + details: notice.details, + }), + }); + } for message in &config.startup_warnings { post_session_configured_events.push(Event { id: "".to_owned(),