From cce059467af64f05d0a1521344847d2b558b6a80 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 8 May 2026 16:15:00 +0300 Subject: [PATCH] [codex] Enable apply_patch freeform by default (#21687) ## Summary - enable `apply_patch_freeform` by default in the feature registry ## Why - make the freeform `apply_patch` tool available by default when model metadata does not explicitly opt into another mode ## Validation - `just fmt` - did not run tests --------- Co-authored-by: Codex --- codex-rs/core/src/config/config_tests.rs | 8 ++++---- codex-rs/core/tests/suite/tools.rs | 14 ++++++++++++++ codex-rs/features/src/lib.rs | 4 ++-- codex-rs/features/src/tests.rs | 2 +- codex-rs/tools/src/tool_config.rs | 2 +- codex-rs/tools/src/tool_config_tests.rs | 24 ++++++++++++++++++++++++ 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 3059bd530..9bda54a9a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -7052,7 +7052,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7472,7 +7472,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7630,7 +7630,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), @@ -7773,7 +7773,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { commit_attribution: None, forced_chatgpt_workspace_id: None, forced_login_method: None, - include_apply_patch_tool: false, + include_apply_patch_tool: true, web_search_mode: Constrained::allow_any(WebSearchMode::Cached), web_search_config: None, use_experimental_unified_exec_tool: !cfg!(windows), diff --git a/codex-rs/core/tests/suite/tools.rs b/codex-rs/core/tests/suite/tools.rs index b85607454..7ba76eaa4 100644 --- a/codex-rs/core/tests/suite/tools.rs +++ b/codex-rs/core/tests/suite/tools.rs @@ -772,6 +772,20 @@ async fn shell_timeout_includes_timeout_prefix_and_metadata() -> Result<()> { "timeout output missing `command timed out`: {stdout}" ); } else { + let normalized_output = output_str + .replace("\r\n", "\n") + .replace('\r', "\n") + .trim_end_matches('\n') + .to_string(); + + let shell_output_pattern = r"(?s)^Exit code: 124\nWall time: [0-9]+(?:\.[0-9]+)? seconds\nOutput:\ncommand timed out after [0-9]+ milliseconds\n(?:.*)?$"; + if Regex::new(shell_output_pattern) + .expect("shell timeout output regex should compile") + .is_match(&normalized_output) + { + return Ok(()); + } + // Fallback: accept the signal classification path to deflake the test. let signal_pattern = r"(?is)^execution error:.*signal.*$"; assert_regex_match(signal_pattern, output_str); diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 6c8ce4989..721069a5b 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -818,8 +818,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::ApplyPatchFreeform, key: "apply_patch_freeform", - stage: Stage::UnderDevelopment, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::ApplyPatchStreamingEvents, diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 5464fa7a6..55d2d8ba2 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -578,7 +578,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config( FeatureConfigSource::default(), FeatureOverrides::default(), ); - assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), false); + assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), true); } #[test] diff --git a/codex-rs/tools/src/tool_config.rs b/codex-rs/tools/src/tool_config.rs index 0bb4b8b15..2ebf0540f 100644 --- a/codex-rs/tools/src/tool_config.rs +++ b/codex-rs/tools/src/tool_config.rs @@ -222,7 +222,7 @@ impl ToolsConfig { let apply_patch_tool_type = match model_info.apply_patch_tool_type { Some(ApplyPatchToolType::Freeform) => Some(ApplyPatchToolType::Freeform), Some(ApplyPatchToolType::Function) => Some(ApplyPatchToolType::Function), - None => include_apply_patch_tool.then_some(ApplyPatchToolType::Freeform), + None => include_apply_patch_tool.then_some(ApplyPatchToolType::Function), }; let agent_jobs_worker_tools = include_agent_jobs diff --git a/codex-rs/tools/src/tool_config_tests.rs b/codex-rs/tools/src/tool_config_tests.rs index 34c871cac..3d1829c4e 100644 --- a/codex-rs/tools/src/tool_config_tests.rs +++ b/codex-rs/tools/src/tool_config_tests.rs @@ -4,6 +4,7 @@ use codex_features::Features; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::PermissionProfile; +use codex_protocol::openai_models::ApplyPatchToolType; use codex_protocol::openai_models::ConfigShellToolType; use codex_protocol::openai_models::InputModality; use codex_protocol::openai_models::ModelInfo; @@ -154,6 +155,29 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() { ); } +#[test] +fn fallback_apply_patch_models_use_function_tool_by_default() { + let model_info = model_info(); + let features = Features::with_defaults(); + + let available_models = Vec::new(); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + available_models: &available_models, + features: &features, + image_generation_tool_auth_allowed: true, + web_search_mode: Some(WebSearchMode::Cached), + session_source: SessionSource::Cli, + permission_profile: &PermissionProfile::Disabled, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + }); + + assert_eq!( + tools_config.apply_patch_tool_type, + Some(ApplyPatchToolType::Function) + ); +} + #[test] fn subagents_keep_request_user_input_config_and_agent_jobs_workers_opt_in_by_label() { let model_info = model_info();