The Cartographer · 11 min mission

Automation & CI: headless Gemini CLI and the GitHub Action

Script Gemini CLI headless and wire it into CI with the official GitHub Action.

gemini-cliautomationci-cdgithub-actionsheadlessscriptingFact-checked 2026-06-15
On this page

Run Gemini CLI as a non-interactive Unix command: pass -p "..." or pipe data into it, and it reads stdin, writes the answer to stdout, and exits with a status code you can branch on. This guide covers the headless flags (-p, --output-format, --approval-mode, exit codes), scripting with jq, and the official google-github-actions/run-gemini-cli GitHub Action.

Trigger headless mode

Gemini runs non-interactively in exactly two cases: input or output is not a TTY (piped or redirected), or you pass a query with -p / --prompt. A bare positional query in a terminal (gemini "explain this project") answers and then continues interactively — only the pipe or the flag makes it one-shot. --prompt text is "Appended to stdin input if provided. Forces non-interactive mode," so piping a file and adding -p combines both into one request.

PatternCommandBehavior
Direct promptgemini -p "Write a poem about TypeScript"Runs once, prints to stdout, exits
Pipe a file (macOS/Linux)cat error.log \| gemini -p "Explain why this failed"stdin + prompt combined into one request
Pipe a file (PowerShell)Get-Content error.log \| gemini -p "Explain why this failed"Same as above on Windows
Pipe a commandgit diff \| gemini -p "Write a commit message for these changes"Feeds the diff plus the instruction
Pipe, no flagcat logs.txt \| geminiPiped stdin alone forces non-interactive; runs once and exits
Headless invocation patterns (verbatim from automation.md / cli-reference.md).
gemini as a one-shot command
… scroll to run this session
Direct prompt prints to stdout and exits; piping does the same. No REPL, no continuation.

Machine-readable output: --output-format

--output-format (alias -o, default text) takes three values. For scripting, use json and extract the answer with jq -r '.response' — a plain string you can assign, test, or pipe onward without scraping prose. Use stream-json only when a step must consume tool calls and message chunks as they stream (e.g. a live CI log); for single-shot extraction, json + jq is simpler.

ValueShapeUse for
textPlain answer (default)Human-readable stdout
jsonOne object: response (string), stats (token/latency metrics), optional errorScripted extraction via jq -r '.response'
stream-jsonNewline-delimited JSON (JSONL), one event per line, ending in resultLive progress / streaming CI logs
--output-format / -o values (cli-reference.md, default `text`).
Extract just the answer with jq
bash
ANSWER=$(gemini --output-format json -p "Summarize CHANGELOG.md in one line" | jq -r '.response')
echo "$ANSWER"
EventCarries
initSession metadata — session ID and the model in use
messageUser and assistant message chunks as they stream
tool_useA tool-call request with its arguments
tool_resultOutput from an executed tool
errorNon-fatal warnings and system errors
resultFinal outcome with aggregated stats and per-model token usage
stream-json emits one of these event objects per line, in order, ending with result (headless.md).

Exit codes

Branch your pipeline on $? (PowerShell: $LASTEXITCODE) instead of grepping stdout. The distinction matters in CI: a 42 means the invocation itself is wrong and a retry won't help, while a 1 may be a transient API blip worth one retry.

CodeMeaningWhat it tells you
0SuccessThe answer on stdout is real; proceed
1General error or API failurePossibly transient — a single retry may be reasonable
42Input errorInvalid prompt or arguments — fix the invocation, do not retry
53Turn limit exceededThe agent ran out of steps — narrow the task or raise the limit
Gemini CLI exit codes (headless.md).

Approval modes for unattended runs

A non-interactive prompt still respects the approval model. By default, when the agent wants to edit a file or run a shell command it asks — and in CI there is no human to answer, so the tool call blocks and the run hangs. Pick an approval mode that won't stop to ask. Combine with --sandbox / -s to run in a sandboxed environment, and --skip-trust to clear the folder-trust prompt for the session.

Flag / valueEffectStatus
--approval-mode=defaultConfirm each actionDefault — blocks unattended runs
--approval-mode=auto_editAuto-approve edits; still confirm shell commandsCurrent
--approval-mode=yoloAuto-approve everythingCurrent
--approval-mode=planRead and propose only — never actsCurrent (safe for read-only steps)
--yolo / -yAuto-approve all actionsDeprecated → use --approval-mode=yolo
--allowed-toolsAllowlist specific toolsDeprecated → use the Policy Engine
--sandbox / -sRun in a sandboxed environmentCurrent
--skip-trustTrust the workspace for this session, skip the folder-trust checkCurrent
Approval / autonomy flags (cli-reference.md). --approval-mode default is `default`.

The official GitHub Action: run-gemini-cli

google-github-actions/run-gemini-cli runs Gemini CLI inside GitHub Actions "both as an autonomous agent for critical routine coding tasks, and an on-demand collaborator." Launched August 6, 2025, still beta; latest release v0.1.22 (2026-04-24); requires Gemini CLI 0.1.18 or later. Workflows reference the major tag @v0; pin to a full SHA in production. The setup centers on the dispatch router (gemini-dispatch.yml): without it, the pre-built sub-workflows (Issue Triage, Pull Request Review, Gemini CLI Assistant) never fire.

Stand up PR review in CI

  1. Scaffold the workflows

    In an interactive session run /setup-github. It writes the dispatch router and pre-built workflows into .github/workflows. Confirm gemini-dispatch.yml is present — the rest depend on it. (You can copy examples/workflows by hand, but you must include the dispatcher.)

  2. Wire credentials

    Add GEMINI_API_KEY as a repository secret (or configure WIF for a credential-less setup). Add .gemini/ and gha-creds-*.json to .gitignore so nothing leaks into the repo.

  3. Set a real prompt

    The prompt input defaults to the literal string You are a helpful assistant. Override it — in the PR-review workflow that means prompt: '/pr-code-review' — or the job runs and reviews nothing.

  4. Constrain what it can do

    Pass a settings JSON string (written to .gemini/settings.json) and use the Policy Engine to allowlist exactly the tools the job needs — not --allowed-tools (deprecated), and avoid blanket yolo where possible.

  5. Harden and pin

    Pin run-gemini-cli to a full commit SHA, keep the dispatcher's author-association gate intact, prefer a custom GitHub App (actions/create-github-app-token from APP_ID + APP_PRIVATE_KEY), and enable branch protection before letting it loose on real PRs.

A workflow step that calls the Action (pin to a SHA in production)
yaml
# .github/workflows/gemini-review.yml (excerpt)
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: 'google-github-actions/run-gemini-cli@v0'
        with:
          gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
          # prompt defaults to "You are a helpful assistant." — override it
          # or the Action does nothing useful.
          prompt: '/pr-code-review'
          # settings is a JSON string written to .gemini/settings.json:
          # use it to configure the Policy Engine / tool allowlists.
          settings: '{ "tools": { "core": ["read_file", "run_shell_command"] } }'

Dispatch routing and the security gate

The router reacts to pull_request, pull_request_review, pull_request_review_comment, issue_comment, and issues events. On demand, people drive it with @gemini-cli comments. The security gate exists because the agent holds credentials and can act — without it, a stranger's comment becomes a prompt-injection and credential-exposure vector. If you hand-roll a workflow instead of using the dispatcher, you must replicate these checks.

TriggerRoutes toGate
@gemini-cli /reviewCode reviewBody startsWith('@gemini-cli') AND author association in OWNER/MEMBER/COLLABORATOR
@gemini-cli /triageIssue triageSame author-association gate
@gemini-cli /approveApprove pathSame author-association gate
@gemini-cli <free text>General invoke handlerSame author-association gate
PR openedAuto code reviewOnly if pull_request.head.repo.fork == false
@gemini-cli comment routing and gating (gemini-dispatch.yml).

Local scripting vs. the GitHub Action

Headless CLI (your shell / cron)

Manage auth via the GEMINI_API_KEY env var, choose --approval-mode and --output-format per invocation, branch on the exit code, and parse .response with jq. Total control — including any safety gates you must add yourself.

run-gemini-cli (in CI)

The dispatcher handles event routing and the OWNER/MEMBER/COLLABORATOR gate. Tool permissions go through the settings JSON / Policy Engine, auth prefers WIF (no long-lived keys), and behavior is wired through inputs like prompt and gemini_model rather than flags.

Knowledge check

You run `gemini -p "refactor utils.ts and run the tests"` inside a CI job. The step never finishes — it just hangs. The single most likely cause?

Reach the end and this star joins your charted sky.