⌂ Home
Gemini CLI: From Zero to Production
Track 5 — Power Usage
📄 Module 15 of 17 ⏱ ~55 min 💪 Advanced
← M13: Google Workspace M15: Memory & Checkpoints →
Module 14 · Track 5: Power Usage

Headless Mode & CI/CD

Gemini CLI was built for the terminal — but it also runs perfectly without one. The --no-interactive flag turns Gemini into a scriptable backend: pipe diffs into it, capture structured JSON back out, wire it into GitHub Actions, Windows Task Scheduler, or pre-commit hooks. This module shows you every pattern for automating Gemini outside of an interactive session.

What You'll Learn

  • Use -p and --no-interactive to run Gemini from scripts and pipelines
  • Pipe stdin into Gemini for diff review, log analysis, and data processing
  • Parse --output-format json results with PowerShell and jq
  • Set up a complete GitHub Actions PR review workflow using the official action
  • Build nightly audit scripts, pre-commit hooks, and file-watch automations in PowerShell
  • Use --yolo safely in trusted pipelines and know when it is dangerous

What Headless Mode Is

Analogy — The Robot on the Assembly Line

Before: Imagine a skilled inspector who reviews parts on an assembly line — but only when they're standing at their workstation, looking at a screen, waiting to respond to prompts. The inspector is brilliant, but the line can only run when they're present.

Pain: Interactive AI tools have the same constraint: they require a human in the loop at every step. That works during development, but not at 2 AM during a nightly build, in a pull request triggered by a bot, or in a scheduled security audit that runs every Sunday.

Mapping: Headless modeRunning Gemini CLI with --no-interactive (or -p flag alone) so it reads a prompt, executes, writes output to stdout, and exits — no terminal UI, no interactive prompts, suitable for scripts and CI pipelines. removes the workstation. Gemini reads its instructions from a flag or stdin, does its work, writes structured output to stdout, and exits with a machine-readable return code. The inspector is now a robot that runs 24/7 without supervision.

Technical Definition — Headless Flags

Headless mode is activated by the -p "prompt" shorthand or the --no-interactive flag (or both). In this mode Gemini CLI skips the REPL loop, does not render the terminal UI, reads any additional input from stdin if provided, executes the prompt, writes output to stdout, and exits with code 0 on success or non-zero on error. The --output-format json flag further wraps the response in machine-parseable JSON. The --yolo flag auto-approves all tool calls (file writes, shell commands) without asking.

Key Flags Reference

These four flags cover 90% of headless automation scenarios.

Flag Effect Typical Use
-p "prompt" Pass the prompt inline; implies headless if no terminal is attached Scripting, cron, pipelines
--no-interactive Explicitly disable the interactive REPL; exit after one turn CI jobs, pre-commit hooks
--output-format json Wrap response in JSON: {"response":"...","model":"..."} Downstream parsing, dashboards
--yolo Auto-approve all tool calls (file writes, shell execs) without prompting Trusted pipelines only — see --yolo section
--model gemini-2.0-flash Override the default model; Flash is faster and cheaper for CI tasks Cost-sensitive pipelines
# Basic: one-shot prompt, exit immediately
gemini -p "Explain what this function does" --no-interactive

# JSON output for downstream parsing
gemini -p "Audit this code for SQL injection" --output-format json --no-interactive

# Specify a faster model to reduce CI cost
gemini -p "Write a changelog entry" --model gemini-2.0-flash --no-interactive

# Auto-approve all tool calls (write files, run shell commands)
# Use ONLY in trusted, isolated environments
gemini -p "Add JSDoc to all exported functions" --yolo --no-interactive
# Basic: one-shot prompt, exit immediately
gemini -p "Explain what this function does" --no-interactive

# JSON output for downstream parsing
gemini -p "Audit this code for SQL injection" --output-format json --no-interactive

# Specify a faster model to reduce CI cost
gemini -p "Write a changelog entry" --model gemini-2.0-flash --no-interactive

# Auto-approve all tool calls (write files, run shell commands)
# Use ONLY in trusted, isolated environments
gemini -p "Add JSDoc to all exported functions" --yolo --no-interactive
WHAT: Each invocation is a single turn — Gemini reads the prompt, executes, prints to stdout, and exits.
WHY: Single-turn exit means the process is scriptable: wrap it in a loop, capture output with $(…) or $( ) subshell syntax, or check $LASTEXITCODE / $? in your CI logic.
GOTCHA: Without --no-interactive, if Gemini detects a TTY it will start the interactive REPL. Always add the flag in scripts to avoid your pipeline hanging forever waiting for keyboard input.
Animation 1 — Headless Execution Pipeline
📥 Trigger: git push / cron / script
⚙️ gemini -p "..." --output-format json --no-interactive
📤 stdout: {"response":"...","exitCode":0}
📊 Consumer: parse JSON → post comment / fail build / write report

What Just Happened?

The animation shows the complete headless flow: a trigger (CI push, cron, or manual script) invokes Gemini with a structured prompt. Gemini runs, exits, and returns JSON on stdout. A downstream consumer (script, dashboard, comment bot) reads that JSON and takes action — all without a human in the loop.

Flags covered. Now the real power: piping your project data into Gemini from stdin.

Reading from stdin — Pipe-Friendly Patterns

Headless mode reads stdin automatically when it is piped. This means you can feed Gemini any data your shell can produce: git diffs, log files, file contents, command output, or CSV data. Gemini reads the piped content as context for the -p prompt.

Common Pipe Patterns

# Pattern 1: Review a diff for security issues
Get-Content changes.diff | gemini -p "Review this diff for security issues. List each issue with severity (HIGH/MEDIUM/LOW) and line number." --no-interactive

# Pattern 2: Auto-generate a conventional commit message
git diff HEAD~1 | gemini -p "Generate a conventional commit message for these changes. Format: type(scope): description" --no-interactive

# Pattern 3: Root-cause an error log
Get-Content error.log | gemini -p "Root cause this error log. Identify the primary exception, likely cause, and suggest a fix." --no-interactive

# Pattern 4: Summarize a large file before editing
Get-Content src/legacy-module.py -Raw | gemini -p "Summarize what this module does, its public API, and any obvious technical debt." --no-interactive

# Pattern 5: Capture output to a variable for processing
$suggestion = git diff HEAD~1 | gemini -p "Write a one-line summary of these changes" --no-interactive
Write-Host "Gemini says: $suggestion"
# Pattern 1: Review a diff for security issues
git diff HEAD~1 | gemini -p "Review this diff for security issues. List each issue with severity (HIGH/MEDIUM/LOW) and line number." --no-interactive

# Pattern 2: Auto-generate a conventional commit message
git diff HEAD~1 | gemini -p "Generate a conventional commit message for these changes. Format: type(scope): description" --no-interactive

# Pattern 3: Root-cause an error log
cat error.log | gemini -p "Root cause this error log. Identify the primary exception, likely cause, and suggest a fix." --no-interactive

# Pattern 4: Summarize a large file before editing
cat src/legacy-module.py | gemini -p "Summarize what this module does, its public API, and any obvious technical debt." --no-interactive

# Pattern 5: Capture output to a variable for processing
suggestion=$(git diff HEAD~1 | gemini -p "Write a one-line summary of these changes" --no-interactive)
echo "Gemini says: $suggestion"
WHAT: The pipe operator feeds file or command output directly as stdin context. Gemini prepends the stdin content to the prompt automatically.
WHY: This avoids the @file reference syntax when the content is dynamically generated at runtime. A git diff does not exist as a named file — it is computed on demand and piped straight through.
GOTCHA: Very large stdin inputs (> 100KB) may be truncated. For large files, use @path/to/file syntax in the prompt instead of piping, as that triggers file reading via the tool API which handles large files more efficiently.
Tip — Exit Codes for CI Integration

Gemini exits with code 0 on success and non-zero on error. In PowerShell, check $LASTEXITCODE. In bash, check $?. You can also instruct Gemini in the prompt to return a specific marker word ("FAIL" / "PASS") and parse stdout for it — which is more reliable than hoping a soft-quality judgment maps to an exit code.

You can now pipe data in. Next: getting structured data back out with --output-format json.

JSON Output for Programmatic Processing

Plain text output is great for humans but painful for automation. The --output-format json flag wraps Gemini's response in a structured JSON envelope that your scripts can reliably parse.

JSON Output Schema

The JSON envelope looks like: {"response": "...", "model": "gemini-2.0-flash", "tokenCount": 342}. The response field contains the model's text output. If you also instruct Gemini (in the prompt) to return JSON inside its response, you get double-encoded JSON — unwrap the outer envelope first, then parse the inner response.

Parsing Results: PowerShell and jq

# ── STEP 1: Run Gemini with JSON output ──────────────────────────────────
# The prompt instructs Gemini to return its own JSON inside the response.
# We ask for a list of security findings.
$prompt = @"
Review the following diff for security vulnerabilities.
Return ONLY valid JSON in this exact shape:
{
  "findings": [
    {"severity": "HIGH|MEDIUM|LOW", "line": 42, "description": "..."}
  ],
  "summary": "one-sentence overall assessment"
}
"@

$rawOutput = git diff HEAD~1 | gemini -p $prompt --output-format json --no-interactive

# ── STEP 2: Unwrap the outer envelope ────────────────────────────────────
# $rawOutput is {"response": "{...inner JSON...}", "model": "..."}
$envelope  = $rawOutput | ConvertFrom-Json
$innerJson = $envelope.response

# ── STEP 3: Parse the inner response JSON ────────────────────────────────
$findings  = $innerJson | ConvertFrom-Json

# ── STEP 4: Act on the findings ──────────────────────────────────────────
$highCount = ($findings.findings | Where-Object { $_.severity -eq "HIGH" }).Count

if ($highCount -gt 0) {
    Write-Error "Security gate failed: $highCount HIGH severity findings detected"
    Write-Host ($findings.findings | ConvertTo-Json -Depth 5)
    exit 1
} else {
    Write-Host "Security gate passed. Summary: $($findings.summary)"
    exit 0
}
#!/usr/bin/env bash
# ── STEP 1: Run Gemini with JSON output ──────────────────────────────────
PROMPT='Review the following diff for security vulnerabilities.
Return ONLY valid JSON:
{"findings":[{"severity":"HIGH|MEDIUM|LOW","line":42,"description":"..."}],"summary":"..."}'

RAW_OUTPUT=$(git diff HEAD~1 | gemini -p "$PROMPT" --output-format json --no-interactive)

# ── STEP 2: Unwrap the outer envelope and parse inner JSON ───────────────
# jq first extracts .response, then parses that string as JSON
INNER=$(echo "$RAW_OUTPUT" | jq -r '.response')
HIGH_COUNT=$(echo "$INNER" | jq '[.findings[] | select(.severity=="HIGH")] | length')

# ── STEP 3: Gate on findings ──────────────────────────────────────────────
if [ "$HIGH_COUNT" -gt 0 ]; then
  echo "Security gate FAILED: $HIGH_COUNT HIGH severity findings"
  echo "$INNER" | jq '.findings[] | select(.severity=="HIGH")'
  exit 1
else
  SUMMARY=$(echo "$INNER" | jq -r '.summary')
  echo "Security gate passed. $SUMMARY"
  exit 0
fi
WHAT: The two-layer parse — outer ConvertFrom-Json / jq to get the envelope, then a second parse on .response — is the standard pattern when you need structured data from Gemini.
WHY: Instructing Gemini to return JSON in its response (rather than prose) is far more reliable for dashboards and CI gates than parsing free text with regex.
GOTCHA: Gemini occasionally wraps JSON in markdown fences (` ```json ... ``` `). Add a post-processing strip: in PowerShell, $innerJson = $innerJson -replace '```json\s*|\s*```',''; in bash, INNER=$(echo "$INNER" | sed 's/^```json//;s/```$//').
You can feed data in and get structured data out. Now wire it into GitHub Actions for fully automated PR reviews.

GitHub Actions Integration

The official google-github-actions/gemini-cli-action@v1 action bundles Gemini CLI, authenticates via Workload Identity, and exposes the same -p / --no-interactive interface you already know. The workflow below reviews every PR and posts findings as a GitHub comment.

PR Review Workflow

.github/workflows/gemini-pr-review.yml
name: Gemini PR Review

on:
  pull_request:
    types: [opened, synchronize]

permissions:
  contents: read          # read the diff
  pull-requests: write    # post comments

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      # ── 1. Check out the PR branch ──────────────────────────────────────
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0     # full history so we can diff against base

      # ── 2. Install and authenticate Gemini CLI ──────────────────────────
      - name: Set up Gemini CLI
        uses: google-github-actions/gemini-cli-action@v1
        with:
          # Store your Gemini API key as a GitHub secret
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}

      # ── 3. Run the diff review ──────────────────────────────────────────
      - name: Review PR diff
        id: review
        run: |
          DIFF=$(git diff origin/${{ github.base_ref }}...HEAD)
          PROMPT="You are a senior code reviewer. Review the following PR diff.
          Return a JSON object:
          {
            \"summary\": \"one paragraph\",
            \"issues\": [{\"severity\":\"HIGH|MEDIUM|LOW\",\"file\":\"...\",\"line\":1,\"message\":\"...\"}],
            \"approvalRecommendation\": \"APPROVE|REQUEST_CHANGES|COMMENT\"
          }
          Diff:
          $DIFF"

          RESULT=$(echo "" | gemini -p "$PROMPT" --output-format json --model gemini-2.0-flash --no-interactive)
          # Strip outer envelope
          INNER=$(echo "$RESULT" | jq -r '.response' | sed 's/^```json//;s/```$//')
          echo "review_json=$INNER" >> $GITHUB_OUTPUT

      # ── 4. Format and post the comment ──────────────────────────────────
      - name: Post review comment
        uses: actions/github-script@v7
        with:
          script: |
            const review = JSON.parse(`${{ steps.review.outputs.review_json }}`);
            let body = `## Gemini Code Review\n\n**Recommendation: \`${review.approvalRecommendation}\`**\n\n${review.summary}\n\n`;

            if (review.issues.length > 0) {
              body += `### Issues Found\n\n`;
              for (const issue of review.issues) {
                const emoji = issue.severity === 'HIGH' ? '🔴' : issue.severity === 'MEDIUM' ? '🟡' : '🟢';
                body += `${emoji} **${issue.severity}** — \`${issue.file}:${issue.line}\` — ${issue.message}\n\n`;
              }
            } else {
              body += `No issues found. ✅\n`;
            }

            body += `\n*Reviewed by Gemini CLI ${new Date().toISOString()}*`;

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body
            });
WHAT: Three-step pattern: diff the PR branch against the base, pipe to Gemini, parse the JSON response, format as Markdown, post via GitHub API.
WHY: fetch-depth: 0 is critical — without full history, git diff origin/main...HEAD fails because the merge-base commit is not available in a shallow clone.
GOTCHA: Never embed GEMINI_API_KEY directly in workflow files. Always use ${{ secrets.GEMINI_API_KEY }}. Add the secret via Settings → Secrets → Actions in your GitHub repo.
Why This Matters

A team of 10 engineers with 5 PRs per day per person means 50 reviews weekly. With this workflow, every PR gets a Gemini first-pass in under 60 seconds, catching obvious issues (SQL injection, hardcoded secrets, missing null checks) before a human reviewer even opens the diff. The average cost per review at Gemini Flash pricing is under $0.01.

CI covered. Now build native Windows automations that run Gemini as a local service.

PowerShell Automation Scripts

Headless Gemini is a first-class PowerShell citizen. The patterns below cover the three most impactful Windows automation scenarios: nightly dependency audits, commit message quality gates, and automatic test generation.

Nightly Dependency Audit

scripts/nightly-audit.ps1
#!/usr/bin/env pwsh
# Nightly dependency security audit — runs via Windows Task Scheduler
# Saves a report to C:\Reports\gemini-audit-YYYY-MM-DD.json

param(
    [string]$ReportDir = "C:\Reports",
    [string]$EmailTo   = ""           # optional: address for alert emails
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$Today = Get-Date -Format "yyyy-MM-dd"

# ── 1. Ensure report directory exists ──────────────────────────────────
New-Item -ItemType Directory -Force -Path $ReportDir | Out-Null

# ── 2. Gather package manifests from current working directory ─────────
$manifests = @()
if (Test-Path "package.json")       { $manifests += "package.json" }
if (Test-Path "requirements.txt")   { $manifests += "requirements.txt" }
if (Test-Path "pyproject.toml")     { $manifests += "pyproject.toml" }
if (Test-Path "Gemfile")            { $manifests += "Gemfile" }

if ($manifests.Count -eq 0) {
    Write-Warning "No package manifests found in $(Get-Location)"
    exit 0
}

$combinedContent = ""
foreach ($file in $manifests) {
    $combinedContent += "=== $file ===`n"
    $combinedContent += (Get-Content $file -Raw)
    $combinedContent += "`n`n"
}

# ── 3. Run Gemini audit ────────────────────────────────────────────────
$prompt = @"
You are a security auditor. Review the following package manifests for:
1. Packages with known CVEs published in the last 90 days
2. Packages pinned to versions older than 1 year with active security advisories
3. Packages with unusual version patterns suggesting supply chain risk

Return ONLY this JSON (no markdown fences):
{
  "auditDate": "$Today",
  "highRisk": [{"package":"","version":"","reason":"","cve":""}],
  "mediumRisk": [{"package":"","version":"","reason":""}],
  "summary": "",
  "upgradeCount": 0
}

Manifests:
$combinedContent
"@

Write-Host "[$Today] Running Gemini dependency audit..."
$raw = ($combinedContent | gemini -p $prompt --output-format json --model gemini-2.0-flash --no-interactive)
$envelope  = $raw | ConvertFrom-Json
$innerText = $envelope.response -replace '```json\s*','' -replace '\s*```',''
$report    = $innerText | ConvertFrom-Json

# ── 4. Save report ────────────────────────────────────────────────────
$reportPath = Join-Path $ReportDir "gemini-audit-$Today.json"
$report | ConvertTo-Json -Depth 10 | Out-File -FilePath $reportPath -Encoding UTF8
Write-Host "Report saved: $reportPath"

# ── 5. Alert on high-risk findings ────────────────────────────────────
if ($report.highRisk.Count -gt 0) {
    Write-Warning "HIGH RISK: $($report.highRisk.Count) package(s) require immediate attention"
    $report.highRisk | ForEach-Object { Write-Warning "  - $($_.package) $($_.version): $($_.reason)" }

    if ($EmailTo) {
        # Requires SMTP relay configured in Windows — adjust server/port as needed
        Send-MailMessage -To $EmailTo -From "gemini-audit@$(hostname)" `
            -Subject "[$Today] Gemini Dependency Audit: $($report.highRisk.Count) HIGH risk findings" `
            -Body $report.summary `
            -SmtpServer "localhost"
    }
    exit 1   # non-zero exit triggers Task Scheduler "on failure" action
}

Write-Host "Audit complete. $($report.upgradeCount) upgrade(s) recommended. No HIGH risk findings."
exit 0

Installing as a Windows Scheduled Task

Install-NightlyAudit.ps1 (run once as Administrator)
# Run once as Administrator to register the nightly audit task
$scriptPath = "$PSScriptRoot\scripts\nightly-audit.ps1"
$action  = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-NonInteractive -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00AM"
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RunOnlyIfNetworkAvailable
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest

Register-ScheduledTask `
    -TaskName   "GeminiNightlyAudit" `
    -Action     $action `
    -Trigger    $trigger `
    -Settings   $settings `
    -Principal  $principal `
    -Description "Nightly Gemini CLI dependency security audit" `
    -Force

Write-Host "Scheduled task registered. Next run: 02:00 AM daily."
WHAT: Register-ScheduledTask creates a Windows Task Scheduler entry. Running under SYSTEM avoids user-session dependency — the audit fires even when no one is logged in.
GOTCHA: SYSTEM does not have your user environment variables. Set GEMINI_API_KEY as a system-wide environment variable (setx /M GEMINI_API_KEY "your-key") or store it in a secrets manager and read it inside the script before calling Gemini.
You can automate audits and generate commit messages. Now the highest-risk flag in the toolbox: --yolo.

--yolo Mode: Auto-Approval for Tool Calls

By default, Gemini pauses before every file write or shell command execution and asks for your confirmation. The --yolo flag--yolo disables all confirmation prompts for tool calls. Gemini will write files, execute shell commands, and install packages without asking. Use only in isolated, trusted environments. Never use in interactive sessions on a development machine. removes all those confirmation prompts — Gemini writes files and runs shell commands immediately and silently.

Warning — --yolo Is a Power Tool

--yolo means Gemini can overwrite any file it has access to and execute any shell command without asking. On a developer workstation, a misbehaving prompt could delete source files, push to git, or exfiltrate secrets to a logging endpoint. Use it only in disposable, sandboxed CI containers where the worst case is a failed build — not a corrupted local repo.

When to Use and When NOT to Use

Scenario Use --yolo? Reason
GitHub Actions runner (ephemeral container) SAFE Container is destroyed after the job; no persistent damage possible
Docker sandbox (--sandbox flag) SAFE Isolated filesystem; host is protected
Nightly cron on a dedicated CI VM with no prod access SAFE VM is isolated and regularly reimaged
Developer laptop / workstation DANGEROUS Can overwrite local files, push git commits, or execute arbitrary commands
Production server or VM with live data DANGEROUS One bad prompt can corrupt the database, delete logs, or expose secrets
Any environment with credentials in env variables DANGEROUS A prompt injection could exfiltrate $env:GEMINI_API_KEY or AWS keys
Animation 2 — --yolo: Safe vs Unsafe Environments
CI Container
○ Container spawns
○ gemini --yolo runs
○ Files modified
○ Tests pass
○ Container destroyed
Developer Laptop
○ gemini --yolo runs
○ Overwrites src/auth.ts
○ Executes git push
○ No undo available
○ Production deploy triggered

What Just Happened?

Left: in a CI container, --yolo is safe because the environment is ephemeral. Right: on a developer laptop, the same flag silently pushes commits and triggers deploys. The flag is identical — the environment is what determines safety.

Safe CI Workflow with --yolo

# Safe: auto-doc generation in GitHub Actions (ephemeral runner)
# This job runs on a runner that is destroyed after completion.
# --yolo is fine here — Gemini can safely write doc files.
Get-ChildItem src/ -Filter "*.ts" | ForEach-Object {
    gemini -p "@$($_.FullName) Add JSDoc comments to all exported functions. Write the result back to the file." `
           --yolo --no-interactive
}

# ANTI-PATTERN — DO NOT DO THIS on a developer machine:
# gemini -p "Refactor the entire codebase" --yolo --no-interactive
#!/usr/bin/env bash
# Safe: auto-doc generation in GitHub Actions (ephemeral runner)
for f in src/**/*.ts; do
    gemini -p "@$f Add JSDoc comments to all exported functions. Write the result back to the file." \
           --yolo --no-interactive
done

# ANTI-PATTERN — DO NOT DO THIS on a developer machine:
# gemini -p "Refactor the entire codebase" --yolo --no-interactive
Animation 3 — End-to-End Headless CI Pipeline
git push
Actions trigger
git diff piped
gemini -p "review"
JSON parsed
PR comment posted

Knowledge Check

1. Which flag is required to prevent Gemini CLI from starting an interactive REPL when called from a script?

--json
--no-interactive
--headless
--silent

2. When you pipe a git diff into Gemini headless mode, how does Gemini receive the piped content?

It is saved to a temp file and referenced as @temp
It is read from stdin and prepended to the -p prompt as context
It requires the --stdin flag to be accepted
Gemini ignores stdin; only the -p flag content is used

3. You run: gemini -p "..." --output-format json --no-interactive and get {"response":"{\"findings\":[...]}","model":"gemini-2.0-flash"}. What is the correct next step in PowerShell?

Directly access .findings on the parsed object
Parse once to get .response, then parse .response again as JSON
Use Select-String to extract the findings array with regex
Add --unwrap-json to avoid the double-encoded format

4. Which of these scenarios makes --yolo safe to use?

A developer laptop where the code is backed up to git
An ephemeral GitHub Actions runner that is destroyed after the job
A VM running in production with env vars for AWS credentials
Any machine where you have administrator privileges

5. What does the fetch-depth: 0 option do in the GitHub Actions checkout step for the PR review workflow?

It limits the diff to the first commit only
It fetches only the HEAD commit to speed up the checkout
It fetches the full git history so git diff origin/main...HEAD can find the merge-base
It prevents any files from being fetched, only metadata

6. Why might Gemini's JSON response need a markdown fence strip before parsing, even when using --output-format json?

The --output-format json flag only affects the outer envelope, not inner content
Gemini occasionally wraps JSON in ```json markdown fences inside the response field
The flag is only available for Gemini Pro, not Flash
Markdown fences are added by the GitHub Actions runner, not Gemini