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
192 changes: 169 additions & 23 deletions .github/workflows/glossary-maintainer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5944,6 +5944,10 @@
"type": "string",
"description": "Custom user agent string for GitHub MCP server configuration (codex engine only)"
},
"command": {
"type": "string",
"description": "Custom executable path for the AI engine CLI. When specified, the workflow will skip the standard installation steps and use this command instead. The command should be the full path to the executable or a command available in PATH."
},
"env": {
"type": "object",
"description": "Custom environment variables to pass to the AI engine, including secret overrides (e.g., OPENAI_API_KEY: ${{ secrets.CUSTOM_KEY }})",
Expand Down
19 changes: 17 additions & 2 deletions pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ func (e *ClaudeEngine) GetRequiredSecretNames(workflowData *WorkflowData) []stri
func (e *ClaudeEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubActionStep {
claudeLog.Printf("Generating installation steps for Claude engine: workflow=%s", workflowData.Name)

// Skip installation if custom command is specified
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
claudeLog.Printf("Skipping installation steps: custom command specified (%s)", workflowData.EngineConfig.Command)
return []GitHubActionStep{}
}

var steps []GitHubActionStep

// Define engine configuration for shared validation
Expand Down Expand Up @@ -214,8 +220,17 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str
}

// Build the command string with proper argument formatting
// Use claude command directly (available in PATH from hostedtoolcache mount)
commandParts := []string{"claude"}
// Determine which command to use
var commandName string
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
commandName = workflowData.EngineConfig.Command
claudeLog.Printf("Using custom command: %s", commandName)
} else {
// Use claude command directly (available in PATH from hostedtoolcache mount)
commandName = "claude"
}

commandParts := []string{commandName}
commandParts = append(commandParts, claudeArgs...)
commandParts = append(commandParts, promptCommand)

Expand Down
14 changes: 14 additions & 0 deletions pkg/workflow/claude_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,17 @@ func TestClaudeEngineNoDoubleEscapePrompt(t *testing.T) {
}
})
}

func TestClaudeEngineSkipInstallationWithCommand(t *testing.T) {
engine := NewClaudeEngine()

// Test with custom command - should skip installation
workflowData := &WorkflowData{
EngineConfig: &EngineConfig{Command: "/usr/local/bin/custom-claude"},
}
steps := engine.GetInstallationSteps(workflowData)

if len(steps) != 0 {
t.Errorf("Expected 0 installation steps when command is specified, got %d", len(steps))
}
}
36 changes: 30 additions & 6 deletions pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ func (e *CodexEngine) GetRequiredSecretNames(workflowData *WorkflowData) []strin
func (e *CodexEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubActionStep {
codexEngineLog.Printf("Generating installation steps for Codex engine: workflow=%s", workflowData.Name)

// Skip installation if custom command is specified
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
codexEngineLog.Printf("Skipping installation steps: custom command specified (%s)", workflowData.EngineConfig.Command)
return []GitHubActionStep{}
}

// Use base installation steps (secret validation + npm install)
steps := GetBaseInstallationSteps(EngineInstallConfig{
Secrets: []string{"CODEX_API_KEY", "OPENAI_API_KEY"},
Expand Down Expand Up @@ -158,10 +164,19 @@ func (e *CodexEngine) GetExecutionSteps(workflowData *WorkflowData, logFile stri
}

// Build the Codex command
// Use regular codex command regardless of firewall status
// PATH will be set to find codex in hostedtoolcache when firewall is enabled
codexCommand := fmt.Sprintf("codex %sexec%s%s%s\"$INSTRUCTION\"",
modelParam, webSearchParam, fullAutoParam, customArgsParam)
// Determine which command to use
var commandName string
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
commandName = workflowData.EngineConfig.Command
codexEngineLog.Printf("Using custom command: %s", commandName)
} else {
// Use regular codex command regardless of firewall status
// PATH will be set to find codex in hostedtoolcache when firewall is enabled
commandName = "codex"
}

codexCommand := fmt.Sprintf("%s %sexec%s%s%s\"$INSTRUCTION\"",
commandName, modelParam, webSearchParam, fullAutoParam, customArgsParam)

// Build the full command with agent file handling and AWF wrapping if enabled
var command string
Expand Down Expand Up @@ -289,18 +304,27 @@ mkdir -p "$CODEX_HOME/logs"
}
} else {
// Build the command without AWF wrapping
// Determine which command to use
var commandName string
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
commandName = workflowData.EngineConfig.Command
codexEngineLog.Printf("Using custom command: %s", commandName)
} else {
commandName = "codex"
}

if workflowData.AgentFile != "" {
agentPath := ResolveAgentFilePath(workflowData.AgentFile)
command = fmt.Sprintf(`set -o pipefail
AGENT_CONTENT="$(awk 'BEGIN{skip=1} /^---$/{if(skip){skip=0;next}else{skip=1;next}} !skip' %s)"
INSTRUCTION="$(printf "%%s\n\n%%s" "$AGENT_CONTENT" "$(cat "$GH_AW_PROMPT")")"
mkdir -p "$CODEX_HOME/logs"
codex %sexec%s%s%s"$INSTRUCTION" 2>&1 | tee %s`, agentPath, modelParam, webSearchParam, fullAutoParam, customArgsParam, logFile)
%s %sexec%s%s%s"$INSTRUCTION" 2>&1 | tee %s`, agentPath, commandName, modelParam, webSearchParam, fullAutoParam, customArgsParam, logFile)
} else {
command = fmt.Sprintf(`set -o pipefail
INSTRUCTION="$(cat "$GH_AW_PROMPT")"
mkdir -p "$CODEX_HOME/logs"
codex %sexec%s%s%s"$INSTRUCTION" 2>&1 | tee %s`, modelParam, webSearchParam, fullAutoParam, customArgsParam, logFile)
%s %sexec%s%s%s"$INSTRUCTION" 2>&1 | tee %s`, commandName, modelParam, webSearchParam, fullAutoParam, customArgsParam, logFile)
}
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/workflow/codex_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,17 @@ func TestCodexEngineHttpMCPServerRendered(t *testing.T) {
})
}
}

func TestCodexEngineSkipInstallationWithCommand(t *testing.T) {
engine := NewCodexEngine()

// Test with custom command - should skip installation
workflowData := &WorkflowData{
EngineConfig: &EngineConfig{Command: "/usr/local/bin/custom-codex"},
}
steps := engine.GetInstallationSteps(workflowData)

if len(steps) != 0 {
t.Errorf("Expected 0 installation steps when command is specified, got %d", len(steps))
}
}
Loading