diff --git a/.github/workflows/gemini-invoke.yml b/.github/workflows/gemini-invoke.yml index 5bec70a52..a308ae184 100644 --- a/.github/workflows/gemini-invoke.yml +++ b/.github/workflows/gemini-invoke.yml @@ -42,7 +42,7 @@ jobs: - name: 'Run Gemini CLI' id: 'run_gemini' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude env: TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' diff --git a/.github/workflows/gemini-issue-fixer.yml b/.github/workflows/gemini-issue-fixer.yml index a19804418..1a72ebedb 100644 --- a/.github/workflows/gemini-issue-fixer.yml +++ b/.github/workflows/gemini-issue-fixer.yml @@ -39,7 +39,7 @@ jobs: uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 - name: 'Run Gemini PR Create' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude id: 'gemini_pr_create' env: GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/gemini-plan-execute.yml b/.github/workflows/gemini-plan-execute.yml index 9dfe61a21..56ce7dd86 100644 --- a/.github/workflows/gemini-plan-execute.yml +++ b/.github/workflows/gemini-plan-execute.yml @@ -44,7 +44,7 @@ jobs: - name: 'Run Gemini CLI' id: 'run_gemini' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude env: TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}' DESCRIPTION: '${{ github.event.pull_request.body || github.event.issue.body }}' diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml index dc525cd74..6d17d5d02 100644 --- a/.github/workflows/gemini-review.yml +++ b/.github/workflows/gemini-review.yml @@ -42,7 +42,7 @@ jobs: uses: 'actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8' # ratchet:actions/checkout@v6 - name: 'Run Gemini pull request review' - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude id: 'gemini_pr_review' env: GITHUB_TOKEN: '${{ steps.mint_identity_token.outputs.token || secrets.GITHUB_TOKEN || github.token }}' diff --git a/.github/workflows/gemini-scheduled-triage.yml b/.github/workflows/gemini-scheduled-triage.yml index 962164576..c21789488 100644 --- a/.github/workflows/gemini-scheduled-triage.yml +++ b/.github/workflows/gemini-scheduled-triage.yml @@ -89,7 +89,7 @@ jobs: id: 'gemini_issue_analysis' if: |- ${{ steps.find_issues.outputs.issues_to_triage != '[]' }} - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude env: GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs ISSUES_TO_TRIAGE: '${{ steps.find_issues.outputs.issues_to_triage }}' diff --git a/.github/workflows/gemini-triage.yml b/.github/workflows/gemini-triage.yml index 22b1413c2..a298164b8 100644 --- a/.github/workflows/gemini-triage.yml +++ b/.github/workflows/gemini-triage.yml @@ -59,7 +59,7 @@ jobs: id: 'gemini_analysis' if: |- ${{ steps.get_labels.outputs.available_labels != '' }} - uses: 'google-github-actions/run-gemini-cli@main' # ratchet:exclude + uses: './' # ratchet:exclude env: GITHUB_TOKEN: '' # Do NOT pass any auth tokens here since this runs on untrusted inputs ISSUE_TITLE: '${{ github.event.issue.title }}' diff --git a/.github/workflows/test-minimal.yml b/.github/workflows/test-minimal.yml new file mode 100644 index 000000000..f5a47ada5 --- /dev/null +++ b/.github/workflows/test-minimal.yml @@ -0,0 +1,21 @@ +name: '🧪 Test Minimal' +on: + workflow_dispatch: + push: + branches: + - 'fix/auth-priority-and-hang' + paths: + - '.github/workflows/test-minimal.yml' + +jobs: + test: + runs-on: 'ubuntu-latest' + steps: + - name: 'Checkout' + uses: 'actions/checkout@v4' + + - name: 'Run Gemini Version' + uses: './' + with: + prompt: '--version' + gemini_debug: true diff --git a/action.yml b/action.yml index 54acd0f94..d6dd630ab 100644 --- a/action.yml +++ b/action.yml @@ -107,7 +107,7 @@ runs: id: 'validate_inputs' shell: 'bash' run: |- - set -exuo pipefail + set -euo pipefail # Emit a clear warning in three places without failing the step warn() { @@ -134,7 +134,7 @@ runs: fi if [[ ${auth_methods} -gt 1 ]]; then - warn "Multiple authentication methods provided. Please use only one of 'gemini_api_key', 'google_api_key', or 'gcp_workload_identity_provider'." + echo "::notice title=Authentication priority::Multiple authentication methods provided. The action will prioritize them in the following order: 1. Workload Identity Federation, 2. Vertex AI API Key, 3. Gemini API Key. Conflicting environment variables will be unset for the CLI." fi # Validate Workload Identity Federation inputs @@ -231,9 +231,14 @@ runs: GEMINI_CLI_VERSION: '${{ inputs.gemini_cli_version }}' EXTENSIONS: '${{ inputs.extensions }}' USE_PNPM: '${{ inputs.use_pnpm }}' + GOOGLE_CLOUD_ACCESS_TOKEN: '${{ steps.auth.outputs.access_token }}' + GOOGLE_GENAI_USE_VERTEXAI: '${{ inputs.use_vertex_ai }}' + GEMINI_API_KEY: '${{ inputs.gemini_api_key }}' + GOOGLE_API_KEY: '${{ inputs.google_api_key }}' shell: 'bash' run: |- set -euo pipefail + mkdir -p ~/.gemini VERSION_INPUT="${GEMINI_CLI_VERSION:-latest}" @@ -260,6 +265,21 @@ runs: echo "Error: Gemini CLI not found in PATH" exit 1 fi + + # Sanitize authentication environment variables to avoid conflicts when installing extensions. + if [[ -n "${GOOGLE_CLOUD_ACCESS_TOKEN:-}" ]]; then + unset GEMINI_API_KEY + unset GOOGLE_API_KEY + unset GOOGLE_APPLICATION_CREDENTIALS + elif [[ "${GOOGLE_GENAI_USE_VERTEXAI:-false}" == "true" && -n "${GOOGLE_API_KEY:-}" ]]; then + unset GEMINI_API_KEY + elif [[ -n "${GEMINI_API_KEY:-}" ]]; then + export GOOGLE_GENAI_USE_VERTEXAI="false" + export GOOGLE_GENAI_USE_GCA="false" + unset GOOGLE_API_KEY + unset GOOGLE_CLOUD_ACCESS_TOKEN + fi + if [[ -n "${EXTENSIONS}" ]]; then echo "Installing Gemini CLI extensions:" echo "${EXTENSIONS}" | jq -r '.[]' | while IFS= read -r extension; do @@ -289,6 +309,31 @@ runs: # Keep track of whether we've failed FAILED=false + # Always unset GOOGLE_APPLICATION_CREDENTIALS to prevent the CLI from hanging on credential discovery + # in environments where multiple auth methods might be present (like GHA). + unset GOOGLE_APPLICATION_CREDENTIALS + + # Sanitize authentication environment variables to avoid conflicts. + # Priority: + # 1. Workload Identity Federation (use_vertex_ai or use_gemini_code_assist with access token) + # 2. Vertex AI API Key (use_vertex_ai with google_api_key) + # 3. Gemini API Key (gemini_api_key) + if [[ -n "${GOOGLE_CLOUD_ACCESS_TOKEN:-}" ]]; then + unset GEMINI_API_KEY + unset GOOGLE_API_KEY + # Unset credential file pointers that might cause conflicts with the access token and hang. + unset GOOGLE_APPLICATION_CREDENTIALS + unset CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE + unset GOOGLE_GHA_CREDS_PATH + elif [[ "${GOOGLE_GENAI_USE_VERTEXAI:-false}" == "true" && -n "${GOOGLE_API_KEY:-}" ]]; then + unset GEMINI_API_KEY + elif [[ -n "${GEMINI_API_KEY:-}" ]]; then + export GOOGLE_GENAI_USE_VERTEXAI="false" + export GOOGLE_GENAI_USE_GCA="false" + unset GOOGLE_API_KEY + unset GOOGLE_CLOUD_ACCESS_TOKEN + fi + # Run Gemini CLI with the provided prompt, using JSON output format # We capture stdout (JSON) to TEMP_STDOUT and stderr to TEMP_STDERR if [[ "${GEMINI_DEBUG}" = true ]]; then @@ -324,12 +369,18 @@ runs: if jq -e . "${TEMP_STDOUT}" >/dev/null 2>&1; then RESPONSE=$(jq -r '.response // ""' "${TEMP_STDOUT}") fi - if jq -e . "${TEMP_STDERR}" >/dev/null 2>&1; then - ERROR_JSON=$(jq -c '.error // empty' "${TEMP_STDERR}") + + # Stderr might contain non-JSON (like stack traces), so we try to extract the last valid JSON object + if grep -q "{" "${TEMP_STDERR}"; then + # Extract the last curly-braced block from stderr + ERROR_CANDIDATE=$(tac "${TEMP_STDERR}" | awk '/^}/{p=1} p; /^{/{if(p)exit}' | tac) + if [[ -n "${ERROR_CANDIDATE}" ]] && jq -e . <<< "${ERROR_CANDIDATE}" >/dev/null 2>&1; then + ERROR_JSON=$(jq -c '.error // empty' <<< "${ERROR_CANDIDATE}") + fi fi - if { [[ -s "${TEMP_STDERR}" ]] && ! jq -e . "${TEMP_STDERR}" >/dev/null 2>&1; }; then - echo "::warning::Gemini CLI stderr was not valid JSON" + if { [[ -s "${TEMP_STDERR}" ]] && [[ -z "${ERROR_JSON}" ]]; }; then + echo "::warning::Gemini CLI stderr contains data but no valid JSON error object was extracted" fi if { [[ -s "${TEMP_STDOUT}" ]] && ! jq -e . "${TEMP_STDOUT}" >/dev/null 2>&1; }; then @@ -338,22 +389,25 @@ runs: # Set the captured response as a step output, supporting multiline - echo "gemini_response<> "${GITHUB_OUTPUT}" + # Use a unique delimiter to avoid collisions + EOF_DELIMITER="gh_gemini_out_$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)" + + echo "gemini_response<<${EOF_DELIMITER}" >> "${GITHUB_OUTPUT}" if [[ -n "${RESPONSE}" ]]; then echo "${RESPONSE}" >> "${GITHUB_OUTPUT}" else cat "${TEMP_STDOUT}" >> "${GITHUB_OUTPUT}" fi - echo "EOF" >> "${GITHUB_OUTPUT}" + echo "${EOF_DELIMITER}" >> "${GITHUB_OUTPUT}" # Set the captured errors as a step output, supporting multiline - echo "gemini_errors<> "${GITHUB_OUTPUT}" + echo "gemini_errors<<${EOF_DELIMITER}" >> "${GITHUB_OUTPUT}" if [[ -n "${ERROR_JSON}" ]]; then echo "${ERROR_JSON}" >> "${GITHUB_OUTPUT}" else cat "${TEMP_STDERR}" >> "${GITHUB_OUTPUT}" fi - echo "EOF" >> "${GITHUB_OUTPUT}" + echo "${EOF_DELIMITER}" >> "${GITHUB_OUTPUT}" # Generate Job Summary if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then