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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions