Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Disable unsupported Codex exec for Copilot
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
  • Loading branch information
Copilot and pelikhan authored Oct 4, 2026
commit 040d22a042168656558f5d07cde7ba03a304aa7d
17 changes: 14 additions & 3 deletions pkg/workflow/codex_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ func (e *CodexEngine) buildNativeConfig(workflowData *WorkflowData, mcpTools []s
Overrides: make(map[string]any),
DisablePlugins: workflowData == nil || len(workflowData.Plugins) == 0,
}
config.Defaults["features"] = map[string]any{"plugins": !config.DisablePlugins}
features := map[string]any{"plugins": !config.DisablePlugins}
if e.ResolveLLMProvider(workflowData) == LLMProviderGitHub {
features["shell_tool"] = false
}
config.Defaults["features"] = features
if isFirewallEnabled(workflowData) {
config.Defaults["model_provider"] = codexOpenAIProxyProviderID
config.Defaults["model_providers"] = map[string]any{
Expand Down Expand Up @@ -118,7 +122,7 @@ func (e *CodexEngine) buildNativeConfig(workflowData *WorkflowData, mcpTools []s
delete(config.Overrides, "mcp_servers")
}
}
if err := validateCodexManagedConfig(config, isFirewallEnabled(workflowData)); err != nil {
if err := validateCodexManagedConfig(config, isFirewallEnabled(workflowData), e.ResolveLLMProvider(workflowData) == LLMProviderGitHub); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -165,7 +169,14 @@ func codexNativeServerDefaults(workflowData *WorkflowData, mcpTools []string) ma
return servers
}

func validateCodexManagedConfig(config *codexNativeConfig, firewallEnabled bool) error {
func validateCodexManagedConfig(config *codexNativeConfig, firewallEnabled, githubProvider bool) error {
if githubProvider {
if features, ok := config.Overrides["features"].(map[string]any); ok {
if shellTool, exists := features["shell_tool"]; exists && shellTool != false {
return errors.New("engine.config: features.shell_tool is disabled for GitHub inference because the Copilot compatibility adapter does not support the Codex exec custom tool")
Comment on lines +173 to +176

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] Root cause only partially addressed: validateCodexManagedConfig only blocks engine.config TOML overrides. But buildCodexCommand appends workflowData.EngineConfig.Args (customArgsParam) as the last segment of the generated codex exec ... command, after shellToolParam ( -c features.shell_tool=false). Since later -c flags win in Codex CLI arg parsing, a workflow with engine: {id: codex, args: ["-c", "features.shell_tool=true"]} silently re-enables the exec tool for GitHub inference, bypassing the new guard entirely.

💡 Suggested fix

Add a check (alongside/near validateCodexManagedConfig, or in buildCodexCommand) that scans workflowData.EngineConfig.Args for a features.shell_tool override when ResolveLLMProvider(workflowData) == LLMProviderGitHub, and rejects it the same way the TOML override is rejected. Add a regression test mirroring TestCodexGitHubInferenceRejectsExecToolOverride but using Args instead of Config.

@copilot please address this.

}
}
}
if rawServers, exists := config.Overrides["mcp_servers"]; exists {
servers, ok := rawServers.(map[string]any)
if !ok {
Expand Down
40 changes: 40 additions & 0 deletions pkg/workflow/codex_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,46 @@ process.stdout.write(serializeConfig(buildConfig({github: {headers: {Authorizati
assert.Equal(t, "Bearer \"quoted\"\\token", headers["Authorization"])
}

func TestCodexGitHubInferenceDisablesUnsupportedExecTool(t *testing.T) {
for _, test := range []struct {
name string
model string
disableShell bool
}{
{name: "Copilot provider", model: "copilot/gpt-5.3-codex", disableShell: true},
{name: "OpenAI provider", model: "gpt-5.3-codex"},
} {
t.Run(test.name, func(t *testing.T) {
data := &WorkflowData{
Name: "provider-tool-config",
Model: test.model,
EngineConfig: &EngineConfig{ID: "codex"},
}
var output strings.Builder
require.NoError(t, renderCodexMCPConfigForTest(t, NewCodexEngine(), &output, map[string]any{}, nil, data))

config := decodeCodexBootstrap(t, output.String())
features := config["features"].(map[string]any)
if test.disableShell {
assert.Equal(t, false, features["shell_tool"])
} else {
assert.NotContains(t, features, "shell_tool")
}
})
}
}

func TestCodexGitHubInferenceRejectsExecToolOverride(t *testing.T) {
_, err := NewCodexEngine().buildNativeConfig(&WorkflowData{
Model: "copilot/gpt-5.3-codex",
EngineConfig: &EngineConfig{
ID: "codex",
Config: "[features]\nshell_tool = true",
},
}, nil)
require.ErrorContains(t, err, "does not support the Codex exec custom tool")
}

func TestCodexNativeConfigShellEnvironment(t *testing.T) {
data := &WorkflowData{
EngineConfig: &EngineConfig{Env: map[string]string{
Expand Down
Loading