From 468d8f848bd85d59b4a86f4e9a1c047ba3b939be Mon Sep 17 00:00:00 2001 From: Yusuke Shimizu Date: Fri, 16 Jan 2026 09:22:18 +0900 Subject: [PATCH 1/2] codex: make skills flag usage conditional The Codex CLI removed the `skills` feature flag in newer versions , causing `goose configure` to fail with exit code 1 when it unconditionally passes `--enable/--disable skills`. This commit updates the logic to check `codex features list` before applying these flags. This ensures the flags are only passed when the installed CLI explicitly advertises support for them. While simply removing the flag usage was an option, this approach is designed to maintain backward compatibility for environments where the CLI version is pinned (e.g., in automation workflows). This also updates the documentation to clarify that the `skills` flag logic is legacy behavior for older CLIs. Signed-off-by: Yusuke Shimizu --- crates/goose/src/providers/codex.rs | 111 +++++++++++++++++++-- documentation/docs/guides/cli-providers.md | 4 +- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/crates/goose/src/providers/codex.rs b/crates/goose/src/providers/codex.rs index abfa54c299d8..9c7453792ba0 100644 --- a/crates/goose/src/providers/codex.rs +++ b/crates/goose/src/providers/codex.rs @@ -39,10 +39,13 @@ pub struct CodexProvider { model: ModelConfig, #[serde(skip)] name: String, - /// Reasoning effort level (low, medium, high) reasoning_effort: String, - /// Whether to enable skills enable_skills: bool, + // NOTE: Codex CLI removed the `skills` feature flag in openai/codex#8850 and now loads + // skills unconditionally. To stay compatible with older Codex CLIs (where `skills` was a + // feature flag), we only pass `--enable/--disable skills` if the installed CLI still + // advertises that flag via `codex features list`. + skills_feature_flag_supported: bool, /// Whether to skip git repo check skip_git_check: bool, } @@ -70,12 +73,16 @@ impl CodexProvider { "high".to_string() }; - // Get enable_skills from config, default to true let enable_skills = config .get_codex_enable_skills() .map(|s| s.to_lowercase() == "true") .unwrap_or(true); + let skills_feature_flag_supported = + Self::supports_feature_flag(&resolved_command, "skills") + .await + .unwrap_or(false); + // Get skip_git_check from config, default to false let skip_git_check = config .get_codex_skip_git_check() @@ -88,10 +95,66 @@ impl CodexProvider { name: Self::metadata().name, reasoning_effort, enable_skills, + skills_feature_flag_supported, skip_git_check, }) } + fn feature_list_contains_feature(stdout: &str, feature: &str) -> bool { + // `codex features list` output is a whitespace-separated sequence of: + // ` ` repeated, e.g.: + // `undo stable false shell_tool stable true ...` + // + // We match by token to be robust to both newline- and space-delimited output. + stdout + .split_whitespace() + .collect::>() + .windows(3) + .any(|w| { + w[0] == feature + && matches!(w[1], "stable" | "beta" | "experimental") + && matches!(w[2], "true" | "false") + }) + } + + fn skills_feature_flag_args( + skills_feature_flag_supported: bool, + enable_skills: bool, + ) -> Option<(&'static str, &'static str)> { + if !skills_feature_flag_supported { + return None; + } + + Some(( + if enable_skills { + "--enable" + } else { + "--disable" + }, + "skills", + )) + } + + async fn supports_feature_flag(command: &PathBuf, feature: &str) -> Result { + let mut cmd = Command::new(command); + configure_command_no_window(&mut cmd); + cmd.arg("features") + .arg("list") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let output = cmd.output().await?; + if !output.status.success() { + return Ok(false); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(Self::feature_list_contains_feature( + stdout.as_ref(), + feature, + )) + } + /// Convert goose messages to a simple text prompt format /// Similar to Gemini CLI, we use Human:/Assistant: prefixes fn messages_to_prompt(&self, system: &str, messages: &[Message]) -> String { @@ -165,6 +228,10 @@ impl CodexProvider { println!("Model: {}", self.model.model_name); println!("Reasoning effort: {}", self.reasoning_effort); println!("Enable skills: {}", self.enable_skills); + println!( + "Skills feature flag supported: {}", + self.skills_feature_flag_supported + ); println!("Skip git check: {}", self.skip_git_check); println!("Prompt length: {} chars", prompt.len()); println!("Prompt: {}", prompt); @@ -189,9 +256,10 @@ impl CodexProvider { self.reasoning_effort )); - // Enable skills if configured - if self.enable_skills { - cmd.arg("--enable").arg("skills"); + if let Some((flag, feature)) = + Self::skills_feature_flag_args(self.skills_feature_flag_supported, self.enable_skills) + { + cmd.arg(flag).arg(feature); } // JSON output format for structured parsing @@ -527,6 +595,7 @@ impl Provider for CodexProvider { "model": model_config.model_name, "reasoning_effort": self.reasoning_effort, "enable_skills": self.enable_skills, + "skills_feature_flag_supported": self.skills_feature_flag_supported, "system_length": system.len(), "messages_count": messages.len() }); @@ -576,6 +645,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -591,6 +661,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -607,6 +678,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -636,6 +708,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -656,6 +729,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -689,6 +763,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -721,6 +796,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -746,6 +822,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -770,6 +847,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -792,6 +870,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -819,6 +898,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -851,6 +931,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -898,6 +979,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -924,6 +1006,7 @@ mod tests { name: "codex".to_string(), reasoning_effort: "high".to_string(), enable_skills: true, + skills_feature_flag_supported: false, skip_git_check: false, }; @@ -952,4 +1035,20 @@ mod tests { fn test_default_model() { assert_eq!(CODEX_DEFAULT_MODEL, "gpt-5.2-codex"); } + + #[test] + fn test_skills_feature_flag_args_based_on_features_list_support() { + let stdout = "undo stable false shell_tool stable true skills stable true steer beta false"; + let skills_supported = CodexProvider::feature_list_contains_feature(stdout, "skills"); + assert!(skills_supported); + + assert_eq!( + CodexProvider::skills_feature_flag_args(skills_supported, true), + Some(("--enable", "skills")) + ); + assert_eq!( + CodexProvider::skills_feature_flag_args(skills_supported, false), + Some(("--disable", "skills")) + ); + } } diff --git a/documentation/docs/guides/cli-providers.md b/documentation/docs/guides/cli-providers.md index cf2f9c57de7a..2b5d4e456443 100644 --- a/documentation/docs/guides/cli-providers.md +++ b/documentation/docs/guides/cli-providers.md @@ -68,7 +68,7 @@ The Codex provider integrates with OpenAI's [Codex CLI tool](https://developers. **Features:** - Uses OpenAI's GPT-5 series models (gpt-5.2-codex, gpt-5.2, gpt-5.1-codex-max, gpt-5.1-codex-mini) - Configurable reasoning effort levels (low, medium, high) -- Optional skills support for enhanced capabilities +- Skills support (Codex CLI native; legacy toggle only on older Codex CLIs) - JSON output parsing for structured responses - Automatic filtering of goose extensions from system prompts @@ -311,7 +311,7 @@ The following models are recognized and passed to the Claude CLI via the `--mode | `GOOSE_MODEL` | Model to use (only known models are passed to CLI) | `gpt-5.2-codex` | | `CODEX_COMMAND` | Path to the Codex CLI command | `codex` | | `CODEX_REASONING_EFFORT` | Reasoning effort level: `low`, `medium`, or `high` | `high` | -| `CODEX_ENABLE_SKILLS` | Enable Codex skills: `true` or `false` | `true` | +| `CODEX_ENABLE_SKILLS` | Legacy toggle for the Codex CLI `skills` feature flag (ignored on newer Codex CLIs where skills are always enabled): `true` or `false` | `true` | | `CODEX_SKIP_GIT_CHECK` | Skip git repository requirement: `true` or `false` | `false` | **Known Models:** From feb0a1c9b119c3d06a85b8ebb167ab8f75a35578 Mon Sep 17 00:00:00 2001 From: Yusuke Shimizu Date: Sun, 18 Jan 2026 14:52:31 +0900 Subject: [PATCH 2/2] codex: add tests for skills feature flag handling - Verify behavior when skills feature is not supported. - Ensure correct arguments are returned for skills flag. Signed-off-by: Yusuke Shimizu --- crates/goose/src/providers/codex.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/goose/src/providers/codex.rs b/crates/goose/src/providers/codex.rs index 9c7453792ba0..07a0d3fa815e 100644 --- a/crates/goose/src/providers/codex.rs +++ b/crates/goose/src/providers/codex.rs @@ -1050,5 +1050,17 @@ mod tests { CodexProvider::skills_feature_flag_args(skills_supported, false), Some(("--disable", "skills")) ); + + let stdout = "undo stable false shell_tool stable true steer beta false"; + let skills_supported = CodexProvider::feature_list_contains_feature(stdout, "skills"); + assert!(!skills_supported); + assert_eq!( + CodexProvider::skills_feature_flag_args(skills_supported, true), + None + ); + assert_eq!( + CodexProvider::skills_feature_flag_args(skills_supported, false), + None + ); } }