GetExecutionSteps returns the GitHub Actions steps for executing Gemini
(workflowData *WorkflowData, logFile string)
| 153 | |
| 154 | // GetExecutionSteps returns the GitHub Actions steps for executing Gemini |
| 155 | func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile string) []GitHubActionStep { |
| 156 | geminiLog.Printf("Generating execution steps for Gemini engine: workflow=%s, firewall=%v", workflowData.Name, isFirewallEnabled(workflowData)) |
| 157 | |
| 158 | var steps []GitHubActionStep |
| 159 | |
| 160 | // Write .gemini/settings.json with context.includeDirectories and tools.core. |
| 161 | // This step runs after the MCP gateway setup (which may have written mcpServers config) |
| 162 | // and merges the context/tools settings into any existing settings.json. |
| 163 | settingsStep := e.generateGeminiSettingsStep(workflowData) |
| 164 | steps = append(steps, settingsStep) |
| 165 | |
| 166 | // Build gemini CLI arguments based on configuration |
| 167 | var geminiArgs []string |
| 168 | |
| 169 | // Model is passed via the native GEMINI_MODEL environment variable only when explicitly |
| 170 | // configured. When not configured, the Gemini CLI uses its built-in default model. |
| 171 | // This avoids embedding the value directly in the shell command (which fails template injection |
| 172 | // validation for GitHub Actions expressions like ${{ inputs.model }}). |
| 173 | modelConfigured := workflowData.EngineConfig != nil && workflowData.EngineConfig.Model != "" |
| 174 | |
| 175 | // Gemini CLI reads MCP config from .gemini/settings.json (project-level) |
| 176 | // The conversion script (convert_gateway_config_gemini.sh) writes settings.json |
| 177 | // during the MCP setup step, so no --mcp-config flag is needed here. |
| 178 | |
| 179 | // Auto-approve all tool executions (equivalent to Codex's --dangerously-bypass-approvals-and-sandbox) |
| 180 | // Without this, Gemini CLI's default approval mode rejects tool calls with "Tool execution denied by policy" |
| 181 | geminiArgs = append(geminiArgs, "--yolo") |
| 182 | |
| 183 | // Skip the workspace trust check so --yolo is not overridden to "default" approval mode. |
| 184 | // Gemini CLI v1.x checks whether the working directory is trusted and overrides --yolo |
| 185 | // with "default" approval mode (exit code 55) when the folder is untrusted. |
| 186 | // GEMINI_CLI_TRUST_WORKSPACE=true (also set in the step env) handles the same case via |
| 187 | // environment variable, but --skip-trust is more reliable when AWF's sandbox does not |
| 188 | // forward all host environment variables into the container. |
| 189 | geminiArgs = append(geminiArgs, "--skip-trust") |
| 190 | |
| 191 | // Add streaming JSON output (JSONL format, compatible with the log parser) |
| 192 | geminiArgs = append(geminiArgs, "--output-format", "stream-json") |
| 193 | |
| 194 | // Note: the --prompt argument is appended raw after shellJoinArgs below because it contains |
| 195 | // a shell command substitution ("$(cat ...)") that must NOT go through shellEscapeArg — |
| 196 | // single-quoting it would prevent shell expansion at runtime. |
| 197 | |
| 198 | // Build the command |
| 199 | commandName := "gemini" |
| 200 | if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" { |
| 201 | commandName = workflowData.EngineConfig.Command |
| 202 | } |
| 203 | |
| 204 | // Append the prompt arg raw (not through shellJoinArgs) to preserve shell expansion |
| 205 | geminiCommand := fmt.Sprintf(`%s %s --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"`, commandName, shellJoinArgs(geminiArgs)) |
| 206 | geminiCommand = getWorkspaceCommandPrefixFor(workflowData.EngineConfig) + geminiCommand |
| 207 | |
| 208 | // Build the full command with AWF wrapping if enabled |
| 209 | var command string |
| 210 | firewallEnabled := isFirewallEnabled(workflowData) |
| 211 | if firewallEnabled { |
| 212 | // Get allowed domains: prefer the pre-warmed cache on WorkflowData to avoid |