Skip to content

Summary output field contains invalid JSON when using json output format #381

@michalszelagsonos

Description

@michalszelagsonos

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}"

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions