From d0c8f422eaaa40b3927b43a0fff7e753e8514f34 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Fri, 7 Aug 2026 01:34:08 +0000 Subject: [PATCH] Configure the default code-mode exec yield timeout (#37352) ## What changed - Add `features.code_mode.default_exec_yield_time_ms`, defaulting to 30 seconds. - Apply the configured default to code-mode `exec` calls that omit `yield_time_ms` and reflect it in the tool description. - Remove the `code_mode_buffered_exec` compatibility feature flag. ## Testing - Cover config loading and tool descriptions for both default and custom yield timeouts. GitOrigin-RevId: 581ffc737b04721c7b5fc81260b1da1cf7d87523 --- codex-rs/core/config.schema.json | 6 ++++ codex-rs/core/src/config/config_tests.rs | 2 ++ codex-rs/core/src/config/mod.rs | 19 +++++++++++- codex-rs/core/src/session/session.rs | 2 +- codex-rs/core/src/session/tests.rs | 4 +-- codex-rs/core/src/tools/code_mode/mod.rs | 22 +++++-------- codex-rs/core/src/tools/spec_plan.rs | 7 +---- codex-rs/core/src/tools/spec_plan_tests.rs | 31 +++++++++++++------ .../src/tools/tool_dispatch_trace_tests.rs | 2 +- codex-rs/features/src/feature_configs.rs | 3 ++ codex-rs/features/src/lib.rs | 4 +-- 11 files changed, 64 insertions(+), 38 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 8177d0f677..f9277577cf 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -365,6 +365,12 @@ "CodeModeConfigToml": { "additionalProperties": false, "properties": { + "default_exec_yield_time_ms": { + "description": "Default yield timeout for code-mode exec calls, in milliseconds.", + "format": "uint64", + "minimum": 0.0, + "type": "integer" + }, "direct_only_tool_namespaces": { "description": "Exact tool namespaces to expose only as direct model tools. These tools bypass deferral, remain top-level in code-mode-only sessions, and are omitted from the nested code-mode tool surface.", "items": { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 19ab4f3346..af14c87f34 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -538,6 +538,7 @@ async fn load_config_resolves_code_mode_config() -> std::io::Result<()> { r#" [features.code_mode] enabled = true +default_exec_yield_time_ms = 10000 excluded_tool_namespaces = ["mcp__codex_apps", "multi_agent_v1"] direct_only_tool_namespaces = ["mcp__history", "mcp__notes"] @@ -554,6 +555,7 @@ disable_in_process_fallback = true ) .await?; + assert_eq!(config.code_mode.default_exec_yield_time_ms, 10_000); assert_eq!( config.code_mode.excluded_tool_namespaces, vec!["mcp__codex_apps".to_string(), "multi_agent_v1".to_string()] diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 61cf285bca..1eaf5f096e 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1117,14 +1117,28 @@ pub struct ToolRegistryConfig { pub error_on_tool_collisions: bool, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] +const DEFAULT_CODE_MODE_EXEC_YIELD_TIME_MS: u64 = 30_000; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct CodeModeConfig { + pub default_exec_yield_time_ms: u64, pub excluded_tool_namespaces: Vec, pub direct_only_tool_namespaces: Vec, /// Keep code mode fail-closed when the standalone host is unavailable. pub disable_in_process_fallback: bool, } +impl Default for CodeModeConfig { + fn default() -> Self { + Self { + default_exec_yield_time_ms: DEFAULT_CODE_MODE_EXEC_YIELD_TIME_MS, + excluded_tool_namespaces: Vec::new(), + direct_only_tool_namespaces: Vec::new(), + disable_in_process_fallback: false, + } + } +} + pub(crate) const DEFAULT_TOKEN_BUDGET_REMINDER_MESSAGE_TEMPLATE: &str = concat!( "Your context window is nearly exhausted (only {n_remaining} tokens remaining) and will be automatically reset for you soon. ", "Once reset, message items in current context window will be cleared in the new window, but notes and history items will be persistent across windows." @@ -2685,6 +2699,9 @@ fn resolve_code_mode_config(config_toml: &ConfigToml) -> CodeModeConfig { }); CodeModeConfig { + default_exec_yield_time_ms: base + .and_then(|config| config.default_exec_yield_time_ms) + .unwrap_or(DEFAULT_CODE_MODE_EXEC_YIELD_TIME_MS), excluded_tool_namespaces: base .and_then(|config| config.excluded_tool_namespaces.as_ref()) .cloned() diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index f390596bb6..b043a83593 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -1204,7 +1204,7 @@ impl Session { executed_tool_calls, code_mode_service: crate::tools::code_mode::CodeModeService::new( Arc::clone(&code_mode_session_provider), - &config.features, + &config.code_mode, ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 5ceb7da2cd..407db25c71 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5827,7 +5827,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { executed_tool_calls, code_mode_service: crate::tools::code_mode::CodeModeService::new( Arc::new(codex_code_mode::DisabledCodeModeSessionProvider), - &config.features, + &config.code_mode, ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), @@ -8046,7 +8046,7 @@ where executed_tool_calls, code_mode_service: crate::tools::code_mode::CodeModeService::new( Arc::new(codex_code_mode::DisabledCodeModeSessionProvider), - &config.features, + &config.code_mode, ), tool_search_handler_cache: Default::default(), turn_environments: Arc::clone(&turn_environments), diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index 6b6a86d81d..f6375e75c0 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -17,13 +17,12 @@ use codex_code_mode::CodeModeSession; use codex_code_mode::CodeModeSessionProvider; use codex_code_mode::CodeModeToolKind; use codex_code_mode::RuntimeResponse; -use codex_features::Feature; -use codex_features::Features; use codex_protocol::models::FunctionCallOutputContentItem; use serde_json::Value as JsonValue; use tokio::sync::OnceCell; use tokio_util::sync::CancellationToken; +use crate::config::CodeModeConfig; use crate::function_tool::FunctionCallError; use crate::original_image_detail::can_request_original_image_detail; use crate::original_image_detail::sanitize_original_image_detail as sanitize_image_detail_items; @@ -54,13 +53,6 @@ pub(crate) use wait_handler::CodeModeWaitHandler; pub(crate) const PUBLIC_TOOL_NAME: &str = codex_code_mode::PUBLIC_TOOL_NAME; pub(crate) const WAIT_TOOL_NAME: &str = codex_code_mode::WAIT_TOOL_NAME; pub(crate) const DEFAULT_WAIT_YIELD_TIME_MS: u64 = codex_code_mode::DEFAULT_WAIT_YIELD_TIME_MS; -const BUFFERED_EXEC_YIELD_TIME_MS: u64 = 30_000; - -pub(crate) fn default_exec_yield_time_override_ms(features: &Features) -> Option { - features - .enabled(Feature::CodeModeBufferedExec) - .then_some(BUFFERED_EXEC_YIELD_TIME_MS) -} /// Returns true for the code-mode `exec` tool in the default namespace. pub(crate) fn is_exec_tool_name(tool_name: &ToolName) -> bool { @@ -78,7 +70,7 @@ pub(crate) struct CodeModeService { session_provider: Arc, availability: Result<(), String>, dispatch_broker: Arc, - default_exec_yield_time_override_ms: Option, + default_exec_yield_time_ms: u64, shutting_down: AtomicBool, unavailable_warning_emitted: AtomicBool, } @@ -86,7 +78,7 @@ pub(crate) struct CodeModeService { impl CodeModeService { pub(crate) fn new( session_provider: Arc, - features: &Features, + config: &CodeModeConfig, ) -> Self { let dispatch_broker = Arc::new(CodeModeDispatchBroker::new()); let availability = session_provider.availability(); @@ -95,7 +87,7 @@ impl CodeModeService { session_provider, availability, dispatch_broker, - default_exec_yield_time_override_ms: default_exec_yield_time_override_ms(features), + default_exec_yield_time_ms: config.default_exec_yield_time_ms, shutting_down: AtomicBool::new(false), unavailable_warning_emitted: AtomicBool::new(false), } @@ -129,9 +121,9 @@ impl CodeModeService { &self, mut request: codex_code_mode::ExecuteRequest, ) -> Result { - if request.yield_time_ms.is_none() { - request.yield_time_ms = self.default_exec_yield_time_override_ms; - } + request + .yield_time_ms + .get_or_insert(self.default_exec_yield_time_ms); self.session().await?.execute(request).await } diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 788ede14c7..1813d08cec 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -4,7 +4,6 @@ use crate::environment_selection::TurnEnvironmentSnapshot; use crate::image_preparation::unified_image_budget_enabled; use crate::session::session::Session; use crate::session::turn_context::TurnContext; -use crate::tools::code_mode::default_exec_yield_time_override_ms; use crate::tools::code_mode::execute_spec::create_code_mode_tool; use crate::tools::context::ToolInvocation; use crate::tools::effective_tool_mode; @@ -758,16 +757,12 @@ fn register_code_mode_executors( ); enabled_tools .sort_by(|left, right| compare_code_mode_tools(left, right, &namespace_descriptions)); - let default_exec_yield_time_ms = - default_exec_yield_time_override_ms(&turn_context.config.features) - .unwrap_or(codex_code_mode::DEFAULT_EXEC_YIELD_TIME_MS); - let execute_handler = CodeModeExecuteHandler::new( create_code_mode_tool( &enabled_tools, &deferred_tools, &namespace_descriptions, - default_exec_yield_time_ms, + turn_context.config.code_mode.default_exec_yield_time_ms, tool_mode == ToolMode::CodeModeOnly, if unified_image_budget_enabled(&turn_context.config.features, &turn_context.model_info) { diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index bdb2cb8b21..d2fd4b4f34 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -1948,17 +1948,28 @@ async fn code_mode_only_exposes_code_executor_and_hides_nested_tools() { } #[tokio::test] -async fn code_mode_buffered_exec_updates_exec_description() { - let plan = probe(|turn| { - set_features(turn, &[Feature::CodeMode, Feature::CodeModeBufferedExec]); - }) - .await; +async fn code_mode_config_updates_exec_description() { + for (configured_yield_time_ms, expected_yield_time_ms) in + [(None, 30_000), (Some(10_000), 10_000)] + { + let plan = probe(|turn| { + set_features(turn, &[Feature::CodeMode]); + if let Some(yield_time_ms) = configured_yield_time_ms { + update_config(turn, |config| { + config.code_mode.default_exec_yield_time_ms = yield_time_ms; + }); + } + }) + .await; - let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else { - panic!("expected code mode exec tool"); - }; - assert!(exec.description.contains("Defaults to 30000 ms.")); - assert!(!exec.description.contains("Defaults to 10000 ms.")); + let ToolSpec::Freeform(exec) = plan.visible_spec(codex_code_mode::PUBLIC_TOOL_NAME) else { + panic!("expected code mode exec tool"); + }; + assert!( + exec.description + .contains(&format!("Defaults to {expected_yield_time_ms} ms.")) + ); + } } #[tokio::test] diff --git a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs index bf9a13b067..fd5cff4fa2 100644 --- a/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs +++ b/codex-rs/core/src/tools/tool_dispatch_trace_tests.rs @@ -279,7 +279,7 @@ async fn missing_code_mode_wait_traces_only_the_wait_tool_call() -> anyhow::Resu let (mut session, turn) = make_session_and_context().await; session.services.code_mode_service = CodeModeService::new( Arc::new(MissingCellCodeModeSessionProvider), - &turn.config.features, + &turn.config.code_mode, ); attach_test_trace(&mut session, &turn, temp.path())?; diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index bbe072424c..0f3e9bec15 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -17,6 +17,9 @@ pub struct ToolRegistryConfigToml { pub struct CodeModeConfigToml { #[serde(skip_serializing_if = "Option::is_none")] pub enabled: Option, + /// Default yield timeout for code-mode exec calls, in milliseconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub default_exec_yield_time_ms: Option, /// Exact tool namespaces to omit from the code-mode nested tool surface. #[serde(skip_serializing_if = "Option::is_none")] pub excluded_tool_namespaces: Option>, diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 06ce37e1e6..a861529b39 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -99,7 +99,7 @@ pub enum Feature { ExecutedToolCallMetadata, /// Enable JavaScript code mode backed by the standalone host process. CodeMode, - /// Use a 30-second default yield timeout for code mode exec calls. + /// Removed compatibility flag for the configurable code-mode exec yield timeout. CodeModeBufferedExec, /// Run JavaScript code mode in the standalone host process. CodeModeHost, @@ -924,7 +924,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::CodeModeBufferedExec, key: "code_mode_buffered_exec", - stage: Stage::UnderDevelopment, + stage: Stage::Removed, default_enabled: false, }, FeatureSpec {