Gemini CLI in GitHub Actions
Every PR your team opens is an opportunity for an automated AI review before a human ever looks at it. The google-github-actions/run-gemini-cli action wires Gemini CLI directly into your CI pipeline — triggering on pull requests, issue creation, or even a comment that says @gemini-cli /review. This module walks through four production-ready workflows you can copy into any repository today.
What You'll Learn
- Understand what the
google-github-actions/run-gemini-cliaction does and when to use it - Write a complete PR review workflow that posts findings as PR comments
- Auto-label and respond to new issues with an issue triage workflow
- Trigger Gemini CLI from a PR comment using
@gemini-cli /review - Run a weekly OWASP Top 10 security scan with JSON output for the GitHub Security tab
- Control costs with the Flash model and handle rate limits gracefully
- Replicate CI behavior locally with PowerShell before pushing
Gemini CLI in CI/CD
Imagine having a senior engineer who reads every single pull request before it goes to human review — checking for security holes, missing tests, and subtle logic bugs. The pain before automated AI review: either reviews get skipped when the team is busy, or the same junior-level issues (hardcoded credentials, no error handling, breaking changes with no migration note) get caught late — sometimes in production. The mapping: Gemini CLI in GitHub Actions is that tireless senior engineer. It runs within 2 minutes of every PR open, posts structured findings as comments, and never has "review fatigue" on Friday afternoons.
The run-gemini-cli actionAn official GitHub Action published by Google that installs the Gemini CLI binary on the Actions runner, authenticates it using a GEMINI_API_KEY secret, and executes a prompt — either inline in the workflow YAML or loaded from a file. It supports the full @ mention syntax and all Gemini CLI flags. is a reusable GitHub Actions step that installs, authenticates, and runs Gemini CLI with a given prompt. It:
- Installs the correct Gemini CLI version on the Ubuntu runner
- Authenticates using the
GEMINI_API_KEYsecret you've stored in GitHub Secrets - Accepts a
promptinput (inline text or@file.mdreference) - Optionally uses a
github_tokento post the output as a PR or issue comment
What the Action Enables
Automated PR Review
Run on every pull_request event. Gemini reviews the diff for correctness, security, and test coverage, then posts findings as a PR comment.
Issue Triage
Run on issues: [opened]. Classify the issue as bug/feature/question, apply labels, and post a clarifying comment if info is missing.
Comment-Triggered Runs
Run on issue_comment: [created]. Any PR comment containing @gemini-cli /review re-triggers the review with the commenter's custom skill.
Weekly Security Scan
Run on a schedule cron. Scans the entire codebase for OWASP Top 10 vulnerabilities and outputs structured JSON for the GitHub Security tab.
PR Review Workflow
This is the most common Gemini CLI CI pattern. Every time a PR is opened or updated, Gemini reviews the changes and posts findings as a PR comment.
name: AI PR Review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
review:
runs-on: ubuntu-latest
# Only run on non-draft PRs
if: github.event.pull_request.draft == false
permissions:
contents: read # checkout
pull-requests: write # post PR comment
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for accurate diff
- name: AI PR Review
uses: google-github-actions/run-gemini-cli@v1
with:
# @ mentions work relative to the checkout root
prompt: |
@. Review this pull request for:
1. Correctness — logic errors, off-by-one bugs, null pointer risks
2. Security vulnerabilities — OWASP Top 10, hardcoded secrets, injection
3. Breaking changes — API surface changes, removed exports, schema migrations
4. Test coverage — are new branches and edge cases tested?
Format your findings as GitHub Flavored Markdown.
Start with a one-paragraph summary. Then list findings
per file with severity (HIGH / MEDIUM / LOW) and
a specific line reference where possible.
If no significant issues are found, say so clearly.
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
# Use Flash for cost efficiency in CI
model: gemini-2.0-flash
if: github.event.pull_request.draft == false condition skips draft PRs — you don't want to waste API quota on work-in-progress code that's not ready for review.
contents: read + pull-requests: write. Never grant broader permissions than needed. The github_token input tells the action to post the Gemini output as a PR comment using the bot's identity.
@. in CI sends the entire checked-out repository. For large monorepos this burns context budget fast. Prefer @src/ or pass only the changed files: git diff origin/main...HEAD -- '*.ts' | gemini -p "Review these changes".
What the PR Comment Looks Like
What Just Happened?
Gemini CLI ran on the Ubuntu runner, read the entire repository via @., and used the PR diff context to identify specific file-line concerns. The github_token input authorized the action to post the formatted output as a bot comment on the PR — no separate API call or custom script required. The whole process took 87 seconds and cost approximately $0.004 using Gemini Flash.
Issue Triage Workflow
New issues often sit unlabeled for days. This workflow fires the moment an issue is opened, classifies it, applies labels, and posts a clarifying comment if the reporter didn't include enough information.
name: AI Issue Triage
on:
issues:
types: [opened]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write # add labels + post comment
contents: read
steps:
- uses: actions/checkout@v4
- name: Triage Issue
uses: google-github-actions/run-gemini-cli@v1
with:
prompt: |
A new GitHub issue has been opened:
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
Classify this issue:
1. TYPE: one of bug / feature-request / question / documentation / duplicate
2. PRIORITY: P0 (production down), P1 (high), P2 (normal), P3 (low)
3. LABELS: comma-separated list of applicable labels from:
bug, enhancement, documentation, question, needs-info,
good-first-issue, help-wanted, wontfix
4. COMMENT: If the issue is a bug report that lacks steps to reproduce,
system info, or expected vs actual behavior — draft a polite comment
asking for the missing information. Otherwise output "NONE".
Output ONLY a JSON object in this exact format, no markdown:
{
"type": "...",
"priority": "...",
"labels": ["...", "..."],
"comment": "..." or null
}
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
model: gemini-2.0-flash
${{ github.event.issue.* }} — GitHub Actions expression syntax. No checkout or file reading is needed here.
gh issue edit and gh issue comment.
Comment-Triggered Runs
Sometimes a reviewer wants to invoke Gemini with a specific focus. Typing @gemini-cli /review in a PR comment triggers a fresh run — and you can pass any skill name to change what Gemini looks for.
@gemini-cli /security-reviewissue_comment: created eventcontains(body, '@gemini-cli') filter/security-review skill on PR diffname: Gemini CLI Comment Trigger
on:
issue_comment:
types: [created]
jobs:
gemini-review:
# Only run on PR comments (not issue comments)
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '@gemini-cli')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v4
with:
# Checkout the PR branch, not main
ref: ${{ github.event.pull_request.head.sha }}
- name: Extract skill from comment
id: extract-skill
run: |
COMMENT="${{ github.event.comment.body }}"
# Extract the skill name after @gemini-cli
SKILL=$(echo "$COMMENT" | grep -oP '(?<=@gemini-cli\s)/\S+' | head -1)
echo "skill=${SKILL:-/review}" >> "$GITHUB_OUTPUT"
- name: Run Gemini CLI Skill
uses: google-github-actions/run-gemini-cli@v1
with:
prompt: |
A developer requested: ${{ steps.extract-skill.outputs.skill }}
@. Apply the requested skill to this pull request and post your
findings as a detailed Markdown review comment. Reference specific
files and line numbers where relevant.
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
model: gemini-2.0-flash
if condition on the job checks both that the comment is on a PR (not a plain issue) and that the body contains @gemini-cli. Without both checks, this workflow would fire on every comment on every issue.
Weekly Security Scan
A scheduled workflow scans the entire codebase for OWASP Top 10 vulnerabilities every Monday morning, outputting structured JSON that GitHub's Security tab can ingest as SARIFStatic Analysis Results Interchange Format — a JSON schema standardized by OASIS that GitHub's Security tab understands. Uploading a SARIF file makes security findings appear in the Security tab with file-line annotations. alerts.
name: Weekly AI Security Scan
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 08:00 UTC
workflow_dispatch: # Also allow manual trigger
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # upload SARIF results
steps:
- uses: actions/checkout@v4
- name: Run OWASP Security Scan
uses: google-github-actions/run-gemini-cli@v1
id: scan
with:
prompt: |
@src/ Perform a comprehensive security audit of this codebase.
Check every file for the following OWASP Top 10 categories:
- A01 Broken Access Control
- A02 Cryptographic Failures (weak algorithms, hardcoded secrets)
- A03 Injection (SQL, command, LDAP, XPath)
- A05 Security Misconfiguration (debug flags, default credentials)
- A06 Vulnerable Components (outdated imports)
- A07 Authentication/Session Failures (weak tokens, no expiry)
- A09 Logging Failures (sensitive data in logs)
Output ONLY a JSON array. Each element must have:
{
"file": "relative/path/to/file.js",
"line": 42,
"severity": "critical|high|medium|low",
"owasp_category": "A03",
"description": "SQL query built with string concatenation",
"recommendation": "Use parameterized queries instead"
}
If no vulnerabilities found, output an empty array: []
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
# Use Pro model for deeper security analysis
model: gemini-2.0-pro
output_file: security-findings.json
- name: Upload to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: security-findings.json
output_file parameter saves Gemini's JSON response to a file on the runner rather than printing it to the log. The follow-up step uploads it to GitHub's Security tab.
Cost & Rate Limits in CI
Running Gemini on every commit can surprise you with a large bill at the end of the month if you don't plan ahead. Here's how to keep costs predictable.
| Workflow | Model | Approx. Cost | Rate Limit Risk |
|---|---|---|---|
| PR review | flash | $0.003–0.008/run | Low |
| Issue triage | flash | $0.0005/run | Very Low |
| Comment trigger | flash | $0.003–0.008/run | Low |
| Weekly security | pro | $0.15–0.40/run | None (weekly) |
Rate Limit Handling
When multiple PRs are opened in a burst (e.g., a large merge day), the Actions runner may hit rate limitsGemini API rate limits are expressed as requests-per-minute (RPM) and tokens-per-minute (TPM) per API key. The Free tier has 15 RPM and 1M TPM. The Paid tier has 2,000 RPM and 4M TPM for Flash. CI workflows should use a Paid API key and implement exponential backoff.. Add a continue-on-error and retry step:
- name: AI PR Review (with retry)
uses: google-github-actions/run-gemini-cli@v1
id: review
continue-on-error: true
with:
prompt: "@. Review this PR for correctness and security."
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
model: gemini-2.0-flash
- name: Handle rate limit — post notice
if: steps.review.outcome == 'failure'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '> AI review temporarily unavailable (rate limit). A human reviewer will cover this PR.'
})
API Key Management
Store your API key as a repository secret named GEMINI_API_KEY (Settings → Secrets and variables → Actions). For organization-wide use, create it as an organization secret so all repos can inherit it. Never commit the key to the repo or print it in workflow logs — GitHub Actions automatically redacts secret values from logs, but only if they're passed via the secrets context.
Local Equivalent — Test Before Pushing
You don't need to push to test a workflow prompt. Run the same analysis locally to tune it before committing the YAML file.
# Replicate the PR review workflow locally (PowerShell)
# This runs the same prompt against your local diff
# 1. Get the diff between your branch and main
$diff = git diff main...HEAD
# 2. Pipe diff to Gemini for review (non-interactive)
$diff | gemini --no-interactive -p @"
Review these changes for:
1. Correctness — logic errors, off-by-one bugs
2. Security — OWASP Top 10, hardcoded secrets
3. Missing tests
Format findings as Markdown with severity labels.
"@
# 3. Or review specific files only
git diff main...HEAD -- "*.ts" "*.js" |
gemini --no-interactive -p "Review these TypeScript/JavaScript changes for security issues"
# 4. Replicate the issue triage prompt locally
$issueBody = @"
Title: App crashes on startup when PORT env var is missing
Body: Traceback shows null pointer at config.js:15
"@
$issueBody | gemini --no-interactive -p "Classify and triage this issue as JSON"
# 5. Test with a specific changed file
gemini --no-interactive `
-p "@src/auth.js Check for OWASP A02 cryptographic failures" `
--model gemini-2.0-flash
# Replicate the PR review workflow locally (bash)
# 1. Pipe git diff directly to Gemini
git diff main...HEAD | gemini --no-interactive -p \
"Review these changes for correctness, security (OWASP Top 10), and missing tests.
Format findings as Markdown with severity labels."
# 2. Review only TypeScript/JavaScript changes
git diff main...HEAD -- '*.ts' '*.js' | \
gemini --no-interactive -p "Review these TypeScript changes for security issues"
# 3. Test the issue triage prompt locally
echo "Title: App crashes on startup\nBody: NPE at config.js:15" | \
gemini --no-interactive -p "Classify and triage this issue as JSON"
# 4. Check a single file
gemini --no-interactive \
-p "@src/auth.js Check for OWASP A02 cryptographic failures" \
--model gemini-2.0-flash
--no-interactive disables the REPL and runs Gemini as a command-line filter — stdin becomes the context, and the output goes to stdout. This is exactly how the Actions runner invokes Gemini CLI under the hood.
Section Complete — GitHub Actions
You have four production-ready workflow templates: PR review, issue triage, comment-triggered runs, and a weekly security scan. You also have a local PowerShell testing pattern to tune prompts before committing. The next module moves from code infrastructure to document infrastructure: Google Workspace integration.
Knowledge Check
1. You want to ensure the PR review workflow skips draft PRs. Which condition should you add to the job?
if: github.event.pull_request.state == 'open'if: github.event.pull_request.draft == falseif: github.event_name != 'draft'pull_request trigger2. The issue triage workflow injects user-supplied issue text into a Gemini prompt. What security concern does this introduce?
3. Why should you prefer gemini-2.0-flash over gemini-2.0-pro for the PR review workflow?
4. The comment-trigger workflow extracts a skill name from a PR comment. What's the most important security control to add?
permissions: security-events: read to limit the action's blast radius
AI Review Summary
This PR adds JWT authentication middleware. The implementation is mostly correct but has two security concerns that should be addressed before merge.
"dev-secret"whenJWT_SECRETenv var is missing. This will silently use an insecure hardcoded secret in production if the env var is not set.verify()call validates signature but the expiry claim must be explicitly checked with{ clockTolerance: 30 }to prevent clock-skew attacks.exp: Date.now() - 1.