google-github-actions / google-github-actions/run-gemini-cli

Summary output field contains invalid JSON when using json output format

Open
#381 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area/workflows github_actions good first issue kind/bug
Dominant language
TypeScript
Stars
2.1k
Forks
285
Avg merge
8h 8m
Merged PRs (30d)
1

Description

TL;DR

The summary output field in the action contains invalid JSON when you enable json output format in the configuration, like this:

{
    "output": {
      "format": "json"
    }
}

This is a bug in the multi line field handling logic where the output file is cat'ed to GITHUB_OUTPUT but it is not done correctly.

Expected behavior

The summary output field should contain valid JSON. For example:

{
    "response": "I have submitted the review. My task is complete.",
    "stats": {}
}
Observed behavior

There are extra characters appended at the end, like this:

{
    "response": "I have submitted the review. My task is complete.",
    "stats": {}
}EOF
  gemini_errors<<EOF
Action YAML
name: "🔎 Review"
description: "Gemini-powered PR review."

inputs:
  github_app_id:
    description: "GitHub App ID"
    required: true
  github_app_private_key:
    description: "GitHub App private key PEM for the Bucky app"
    required: true
  gemini_api_key:
    description: "Gemini API key"
    required: true
  additional_context:
    description: "Any additional context from the request"
    required: false
    default: ""
  gemini_cli_version:
    description: "Gemini CLI version"
    required: false
    default: ""
  gemini_model:
    description: "Gemini model"
    required: false
    default: ""

runs:
  using: "composite"
  steps:
    - name: Mint identity token
      id: mint_identity_token
      uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b
      with:
        app-id: ${{ inputs.github_app_id }}
        private-key: ${{ inputs.github_app_private_key }}
        permission-contents: read
        permission-issues: write
        permission-pull-requests: write

    - name: Run Gemini pull request review
      id: gemini_pr_review
      uses: google-github-actions/run-gemini-cli@v0
      env:
        GITHUB_TOKEN: ${{ steps.mint_identity_token.outputs.token }}
        ISSUE_TITLE: ${{ github.event.pull_request.title || github.event.issue.title }}
        ISSUE_BODY: ${{ github.event.pull_request.body || github.event.issue.body }}
        PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
        REPOSITORY: ${{ github.repository }}
        ADDITIONAL_CONTEXT: ${{ inputs.additional_context }}
      with:
        gemini_api_key: ${{ inputs.gemini_api_key }}
        gemini_cli_version: ${{ inputs.gemini_cli_version }}
        gemini_model: ${{ inputs.gemini_model }}
        gemini_debug: false

        settings: |
          {
            "general": {
              "disableAutoUpdate": false
            },
            "output": {
              "format": "json"
            },
            "context": {
              "fileName": "AGENTS.md"
            },
            "model": {
              "maxSessionTurns": 25,
              "chatCompression": {
                "contextPercentageThreshold": 0.7
              }
            },
            "telemetry": {
              "enabled": false
            },
            "privacy": {
              "usageStatisticsEnabled": false
            },
            "ui": {
              "showMemoryUsage": true
            },
            "mcpServers": {
              "github": {
                "command": "docker",
                "args": [
                  "run",
                  "-i",
                  "--rm",
                  "-e",
                  "GITHUB_PERSONAL_ACCESS_TOKEN",
                  "ghcr.io/github/github-mcp-server:v0.19.0"
                ],
                "includeTools": [
                  "add_comment_to_pending_review",
                  "pull_request_read",
                  "pull_request_review_write"
                ],
                "env": {
                  "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
                }
              }
            },
            "tools": {
              "core": [
                "glob",
                "google_web_search",
                "list_directory",
                "read_file",
                "read_many_files",
                "run_shell_command(cat)",
                "run_shell_command(echo)",
                "run_shell_command(git)",
                "run_shell_command(grep)",
                "run_shell_command(head)",
                "run_shell_command(nl)",
                "run_shell_command(tail)",
                "search_file_content",
                "web_fetch"
              ]
            }
          }

        prompt: |-
          ...
    - name: Process summary
      id: process_summary
      shell: bash
      if: always()
      env:
        GEMINI_SUMMARY: ${{ steps.gemini_pr_review.outputs.summary }}
      run: |
        set -uo pipefail
        echo "::group::Process summary"

        # the output can have junk after the JSON structure so use jq to remove it by having it parse from { to }
        JSON_OBJ="$(echo "$GEMINI_SUMMARY" | jq -R -s 'match("(?s)\\{.*\\}") | .string | fromjson')"
        
        # verify it
        echo "$JSON_OBJ" | jq empty
        
        # safely output multiline string
        DELIMITER="__GEMINI_EOF__"
        {
          echo "json<<${DELIMITER}"
          echo "${JSON_OBJ}"
          echo "${DELIMITER}"
        } >> "${GITHUB_OUTPUT}"

        echo "::endgroup::"

    - name: Logs
      if: always()
      shell: bash
      env:
        GEMINI_SUMMARY: ${{ steps.gemini_pr_review.outputs.summary }}
        GEMINI_SUMMARY_JSON: ${{ steps.process_summary.outputs.json }}
        GEMINI_ERROR: ${{ steps.gemini_pr_review.outputs.error }}
      run: |
        set -uo pipefail
        
        # show the plain response in text
        echo "::group::Gemini response"
        echo "$GEMINI_SUMMARY_JSON" | jq -r .response
        echo "::endgroup::"

        # dump the entire summary
        echo "::group::Gemini output"
        echo "$GEMINI_SUMMARY"
        echo "::endgroup::"

        # dump the errors
        echo "::group::Gemini errors"
        echo "$GEMINI_ERROR"
        echo "::endgroup::"
Log output
Run google-github-actions/run-gemini-cli@v0
Run set -exuo pipefail
+ auth_methods=0
+ [[ true == \t\r\u\e ]]
+ (( ++auth_methods ))
+ [[ false == \t\r\u\e ]]
+ [[ false == \t\r\u\e ]]
+ [[ 1 -eq 0 ]]
+ [[ 1 -gt 1 ]]
+ [[ false == \t\r\u\e ]]
+ [[ false == \t\r\u\e ]]
+ [[ true == \t\r\u\e ]]
+ [[ false == \t\r\u\e ]]
+ [[ false == \t\r\u\e ]]
Run mkdir -p .gemini/
Run set -euo pipefail
Run set -euo pipefail
Installing Gemini CLI from npm: @google/gemini-cli@latest
Verifying installation:
0.10.0
Run set -euo pipefail
  
Run set -uo pipefail
Process summary
Run set -uo pipefail
Gemini response
  I have submitted the review. My task is complete.
Gemini output
  {
    "response": "I have submitted the review. My task is complete.",
    "stats": {
      "models": {
        "gemini-2.5-pro": {
          "api": {
            "totalRequests": 8,
            "totalErrors": 0,
            "totalLatencyMs": 40355
          },
          "tokens": {
            "prompt": 280112,
            "candidates": 612,
            "total": 282069,
            "cached": 194828,
            "thoughts": 1345,
            "tool": 0
          }
        }
      },
      "tools": {
        "totalCalls": 7,
        "totalSuccess": 7,
        "totalFail": 0,
        "totalDurationMs": 5691,
        "totalDecisions": {
          "accept": 0,
          "reject": 0,
          "modify": 0,
          "auto_accept": 7
        },
        "byName": {
          "read_file": {
            "count": 1,
            "success": 1,
            "fail": 0,
            "durationMs": 5,
            "decisions": {
              "accept": 0,
              "reject": 0,
              "modify": 0,
              "auto_accept": 1
            }
          },
          "pull_request_read": {
            "count": 2,
            "success": 2,
            "fail": 0,
            "durationMs": 822,
            "decisions": {
              "accept": 0,
              "reject": 0,
              "modify": 0,
              "auto_accept": 2
            }
          },
          "pull_request_review_write": {
            "count": 2,
            "success": 2,
            "fail": 0,
            "durationMs": 2408,
            "decisions": {
              "accept": 0,
              "reject": 0,
              "modify": 0,
              "auto_accept": 2
            }
          },
          "add_comment_to_pending_review": {
            "count": 2,
            "success": 2,
            "fail": 0,
            "durationMs": 2456,
            "decisions": {
              "accept": 0,
              "reject": 0,
              "modify": 0,
              "auto_accept": 2
            }
          }
        }
      },
      "files": {
        "totalLinesAdded": 0,
        "totalLinesRemoved": 0
      }
    }
  }EOF
  gemini_errors<<EOF
Gemini errors
Additional information

The problem is here, with these lines in action.yml, ~line 272:

# Set the captured response as a step output, supporting multiline
echo "gemini_response<<EOF" >> "${GITHUB_OUTPUT}"
cat "${TEMP_STDOUT}" >> "${GITHUB_OUTPUT}"
echo "EOF" >> "${GITHUB_OUTPUT}"

This does not terminate the redirection correctly and ends up picking up the extra output until the next redirection is started. This works correctly:

# safely output multiline string using a more unique delimiter
DELIMITER="__GEMINI_EOF__"
{
  echo "gemini_response<<${DELIMITER}"
  cat "${TEMP_STDOUT}"
  echo "${DELIMITER}"
} >> "${GITHUB_OUTPUT}"

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in action.yml around line 272, where the captured response is written to GITHUB_OUTPUT using the EOF delimiter. Reproduce the JSON-format workflow and inspect the generated summary output. Done means the summary parses as valid JSON and does not include trailing error output.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, shell
Domain
ci-cd, devops
Issue type
Bug
Difficulty
1/5
Estimated time
Under an hour
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.