Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
11 changes: 2 additions & 9 deletions pkg/workflow/schemas/github-workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,8 @@
"queue": {
"$comment": "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-queueing-multiple-pending-runs",
"description": "Controls how pending jobs or workflow runs are queued within a concurrency group. With the default `single`, at most one run can be pending — additional pending runs cancel the previous one. With `max`, up to 100 runs can be pending and are processed in FIFO order. The combination of `queue: max` and `cancel-in-progress: true` is not allowed.",
"oneOf": [
{
"type": "string",
"enum": ["single", "max"]
},
{
"$ref": "#/definitions/expressionSyntax"
}
],
"type": "string",
"enum": ["single", "max"],
Comment on lines +45 to +46

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] Confirmed by running go test ./pkg/workflow/ -run TestConcurrencyQueueSchemaValidation: this change makes the existing test fail, because queue with a GitHub Actions expression (e.g. ${{ github.event_name == 'pull_request' && 'single' || 'max' }}) is a documented, tested case that the compiler generates (see concurrency_command_test.go's mixed-PR scenario). Dropping the expressionSyntax branch regresses that feature, not just a theoretical risk.

💡 Test failure output
--- FAIL: TestConcurrencyQueueSchemaValidation (0.01s)
Error: GitHub Actions schema validation failed ... '/concurrency/queue': value must be one of 'single', 'max'

Restore the oneOf with expressionSyntax for queue, or update/remove TestConcurrencyQueueSchemaValidation's expression case if dropping expression support for queue is intentional — but as-is this is an unrelated regression bundled into a Codex-provider fix PR.

@copilot please address this.

"default": "single"
}
},
Expand Down
Loading