This document provides a technical walkthrough of how the Gemini PR Review & Triage action is implemented, how it handles codebase context, and how the core Python scripts function.
The action runs as a native GitHub Composite Action (action.yml) that boots a Python environment using uv for dependency management.
The code review action is organized into a modular Python package (gemini_review/) separating domain responsibilities, with gemini_pr_review.py serving as the top-level execution entrypoint and backward-compatible API facade:
gemini_review/schemas.py: Pydantic schemas defining structured outputs (InlineComment,ReviewResult).gemini_review/config.py: Configuration loader forgemini-review.tomland timeout defaults.gemini_review/utils.py: Binary file exclusion, diff patch line parsing, token counting, repo file listing, and rule loaders.gemini_review/github.py: GitHub REST API client functions for PR files, comment threads, and review postings.gemini_review/skills.py: Agent skill metadata parser and instruction loader for workspace and built-in skills.gemini_review/developer_knowledge.py: MCP/RPC integration to search and fetch official Google developer documentation.gemini_review/prompts.py: Dynamic PR diff prompt construction, full/sparse codebase context generation, and prompt assembly.gemini_pr_review.py: Main CLI entrypoint script that re-exports allgemini_reviewpackage APIs and runs the primary review loop.
The PR review workflow is designed to retrieve PR details, collect local codebase context, build a structured prompt, and atomically submit line-specific reviews back to GitHub.
- PR Changes: The script fetches the list of changed files and their diff patches from the GitHub API using
get_pr_files(). Locally, it falls back togit diff main...HEAD. - Binary Exclusion: Non-text files, binaries, lock files, and encrypted files are filtered out using
is_text_file().
To provide Gemini with project-wide awareness, the script traverses the workspace to find all tracked files via get_all_repo_files(). It then sums the file sizes (excluding the changed PR files) to determine the context mode:
- Full Context Mode (≤ 1.5 MB):
If the rest of the text files in the repository fit within the size limit, the script reads their full contents using
get_file_content()and appends them to the prompt under the section=== Repository Context (Full Codebase) ===. - Sparse Context Mode (> 1.5 MB):
If the repository exceeds the threshold, the action activates Sparse Context Mode:
- Visual Directory File Tree: Generates a structured representation of the codebase using
generate_file_tree(). - Core Manifests & Documentation: Reads full contents of key configuration and root documentation files matching
core_file_patterns(e.g.README*,CONTRIBUTING*,ARCHITECTURE*,GEMINI.md,package.json,go.mod,pyproject.toml) viais_core_file(), up to a configurablemax_core_context_bytesbudget (default 500 KB). - Dynamic Context Selection: Invokes a structured Gemini model call via
select_dynamic_context_files()to analyze the PR diff and evaluate non-core repository files against a 4-tier architectural prioritization framework. It dynamically selects up to 8 of the most relevant sister modules, utilities, domain/algorithmic precedents, or unit test files to attach directly into the review prompt.
- Visual Directory File Tree: Generates a structured representation of the codebase using
To drastically reduce API costs and latency for large codebase contexts, gemini_pr_review.py incorporates native Gemini Context Caching:
- Threshold Verification: If the codebase context exceeds 100,000 characters (~32,768 tokens, Gemini's minimum caching requirement), context caching is automatically activated.
- Deterministic Active Cache Lookup (
client.caches.list()): Before creating a new cache, the script queries active server-side caches matching the model and persona-scoped repository display name (repo-cache-{repo}-{model}-{persona}). It validates that the cache's model matches the requested model, skipping any caches created under a different model version to avoidINVALID_ARGUMENTerrors. - Stateless Zero-Infrastructure Persistence: No local state, runner disk storage, or database is required between workflow runs. Active context caches are queried dynamically on Gemini's API servers using the deterministic display name key.
- Tenant Isolation & Security: Context caches are hosted on Google's infrastructure and strictly isolated to your API key / GCP project namespace. No third-party or unauthorized API key can access or view cached context.
- Cache Provisioning (
client.caches.create()): If no matching active cache handle for the current model is found, the script provisions a newCachedContentresource containing the codebase context,system_instruction, and pre-parsedtools. - Cost & Multi-Turn Optimisation: Input tokens billed against the cached handle receive a 90% discount. Furthermore, multi-turn tool interactions (such as Google Developer Knowledge MCP searches or skill lookups) reference the cached handle without re-billing the codebase context on subsequent turns.
- Resilient Fallback: If cache creation, lookup, or generation with cached content fails for any reason, the script seamlessly falls back to direct context generation without interrupting the CI review pipeline.
When enabled via include_comment_history: 'true' (default), gemini_pr_review.py fetches complete historical discussion context from the GitHub API:
- Inline & Conversation Retrieval (
get_pr_comments()): Fetches inline review comments (pulls/{pr_number}/comments) and general PR issue comments (issues/{pr_number}/comments) usingwhile Truepagination loops (per_page=100) to guarantee all historical comments are captured. - Thread Structuring (
format_pr_comment_history()): Groups comments into root comments and nested developer replies per file and line number, presenting clear conversational timelines to Gemini. - Resolution Decision Matrix: Instructs Gemini not to repeat suggestions that have been addressed in code, deferred, or explicitly justified by developers, while ensuring unresolved items without explanation or un-applied agreed fixes are re-flagged.
Gemini is forced to return structured JSON adhering to the Pydantic schemas:
InlineComment:path: File path.line: Line number in the RIGHT (modified) side of the diff.side: Diff side.severity: Severity icon (🔴,🟠,🟡,🟢).comment_text: Feedback string.code_suggestion: Optional drop-in suggestion replacement.
ReviewResult:summary: High-level quality assessment.resolved_items: List of previously raised review comments/threads resolved in the current PR iteration.general_feedback: List of highlights or observations.comments: List ofInlineCommentinstances.
Submitting reviews with line-specific comments via GitHub's API can be fragile (e.g. if the model specifies a line index that falls outside the diff range).
- Atomic Run: The script first attempts to post the summary, resolved items list (
### ✅ Resolved Items from Prior Reviews), and all inline comments in a single transaction viaPOST /repos/{owner}/{repo}/pulls/{number}/reviews. - Resilient Fallback: If the atomic post fails (e.g. returns HTTP 422), the script catches the failure, posts the review summary comment, and attempts to publish individual comments one-by-one. This ensures valid comments are still delivered while preventing a CI checkout block.
The issue triage script automatically categorises and labels new issues to streamline management.
- The script calls
get_available_labels()to fetch all labels currently configured on the repository, handling pagination dynamically.
- The system instruction (loaded from
gemini-triage.toml) instructs the model to act as a triage assistant. - The issue's title and body, along with the list of available labels, are passed to Gemini.
- Using structured output, Gemini returns a
TriageResultcontaining:selected_labels: The subset of repo labels that match the issue.reasoning: The explanation for applying those labels.
- The script calls
apply_labels()to add the selected labels to the issue on GitHub.
The action's behavior is configured via gemini-review.toml:
# Default configuration
description = "Reviews a pull request using Google Gemini"
prompt = "..."
# Codebase Context Configuration (Optional)
max_context_bytes = 1500000 # Size threshold in bytes to trigger Sparse Mode
core_file_patterns = [
"*.md",
"pyproject.toml", "package.json", "go.mod", "Cargo.toml", "pom.xml",
"build.gradle", "build.gradle.kts", "settings.gradle", "Gemfile",
"composer.json", "*.csproj", "*.sln", "Dockerfile", "docker-compose.yml",
"gemini-review.toml", "action.yml"
]
# Gemini Context Caching (Optional)
enable_context_caching = true # Enable native Gemini Context Caching for large repos (default: true)
cache_ttl_seconds = 3600 # Cache TTL in seconds (default: 3600 / 1 hour)Google provides Google Managed Agents (such as the Antigravity Agent) via the stateful Interactions API (interactions.create). Managed agents execute inside a Google-hosted, OS-isolated Linux sandbox VM equipped with native tool harnesses (file mounting, shell execution, web search) and multi-turn state persistence.
During the architectural design of this action, I evaluated using Managed Agents (interactions.create) versus the direct Gemini Model API (client.models.generate_content) paired with native Gemini Context Caching (client.caches).
| Dimension | Direct Gemini API + Context Caching (Selected) | Managed Agents / Interactions API |
|---|---|---|
| API Primitives | client.models.generate_content + client.caches |
client.interactions.create + agent_config |
| Execution Environment | Client-side Python runner on GitHub Runner | Google-hosted cloud Linux sandbox VM |
| Codebase Context Strategy | Native Gemini Context Caching (CachedContent) |
Repo mounted in VM sandbox via environment sources |
| Input Token Pricing | 90% discount on cached tokens (>100k chars) | Standard token pricing per interaction turn |
| Startup Overhead & Latency | Near-zero (direct token generation) | VM sandbox boot & cold-start latency per job |
| State Management | Stateless single-turn pass per PR commit | Persistent multi-turn sessions (previous_interaction_id) |
| API Maturity | GA (General Availability) | Public Preview |
I selected the Direct Gemini API + Context Caching model based on the following technical trade-offs:
-
Latency & CI Execution Speed: Code review in a CI pipeline requires rapid execution feedback. Managed Agent sandboxes incur non-negligible cold-start boot latency when provisioning remote containers for each workflow trigger. Direct API generation starts producing feedback immediately.
-
Context Caching Cost Optimisation (90% Input Token Savings): For repositories with large codebase contexts (>100,000 characters), my context caching engine creates a server-side
CachedContenthandle that persists across workflow runs. Subsequent PR review runs referencing the cache receive a 90% discount on cached input tokens. Managed agent environments load context into a remote VM container filesystem, which does not benefit from nativeCachedContentinput token discounts. -
Appropriate Complexity for CI Workflows: A Pull Request review is inherently a single-turn structured evaluation per git commit. The heavyweight architecture of a persistent Linux VM sandbox container (with mounted tool shims and session state tracking) introduces unnecessary complexity compared to a focused Python execution loop running on the native GitHub Action runner.
-
Security & Credential Scope: Passing repository access tokens into remote cloud VM sandboxes (even with egress proxy header transforms) broadens the credential trust boundary. Running the review logic locally on the ephemeral GitHub runner ensures standard GitHub Actions secret isolation.
-
API Stability: Direct API generation and Context Caching primitives are generally available (GA) with guaranteed SLAs, avoiding reliance on Public Preview APIs for production CI pipelines.
