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
This commit is contained in:
pakrym-oai
2026-08-07 01:41:54 +00:00
committed by copyberry
parent 9457f8f734
commit d0c8f422ea
11 changed files with 64 additions and 38 deletions
+6
View File
@@ -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": {
+2
View File
@@ -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()]
+18 -1
View File
@@ -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<String>,
pub direct_only_tool_namespaces: Vec<String>,
/// 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()
+1 -1
View File
@@ -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),
+2 -2
View File
@@ -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),
+7 -15
View File
@@ -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<u64> {
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<dyn CodeModeSessionProvider>,
availability: Result<(), String>,
dispatch_broker: Arc<CodeModeDispatchBroker>,
default_exec_yield_time_override_ms: Option<u64>,
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<dyn CodeModeSessionProvider>,
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<codex_code_mode::StartedCell, String> {
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
}
+1 -6
View File
@@ -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)
{
+21 -10
View File
@@ -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]
@@ -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())?;
+3
View File
@@ -17,6 +17,9 @@ pub struct ToolRegistryConfigToml {
pub struct CodeModeConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<bool>,
/// Default yield timeout for code-mode exec calls, in milliseconds.
#[serde(skip_serializing_if = "Option::is_none")]
pub default_exec_yield_time_ms: Option<u64>,
/// Exact tool namespaces to omit from the code-mode nested tool surface.
#[serde(skip_serializing_if = "Option::is_none")]
pub excluded_tool_namespaces: Option<Vec<String>>,
+2 -2
View File
@@ -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 {