ci: add Antigravity PR reviewer (self-hosted, Ultra) - #49
Conversation
Installs the Antigravity PR reviewer from the template at ~/Code/Local_Only-Projects/antigravity-pr-review via install-into-repo.sh --with-style-guide: - .github/workflows/antigravity-review.yml — triggers on PR open + /agy-review comment, runs on the self-hosted `agy`-labelled runner. - scripts/agy-review.sh + scripts/_agy_print.sh — the reviewer (diff → prompt → agy → PR comment) and its PTY fallback helper. - .github/agy-review.md — the starter review style guide. No repository secrets: the job uses GITHUB_TOKEN to post and the local agy OAuth session for the model. .gitignore is intentionally left untouched (the reviewer writes only to temp dirs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds an Antigravity-based GitHub Actions review flow. It loads repository review guidance, resolves pull-request events, fetches and truncates diffs, runs the CLI with PTY fallbacks, recovers empty output from SQLite, and replaces earlier automated review comments. ChangesAntigravity review flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant GitHub
participant Workflow
participant agy-review.sh
participant gh
participant agy
GitHub->>Workflow: trigger on PR event or /agy-review comment
Workflow->>agy-review.sh: execute review script
agy-review.sh->>gh: fetch PR metadata and diff
agy-review.sh->>agy: submit review prompt
agy->>agy-review.sh: return review text
agy-review.sh->>gh: delete marked comment and post new review
🚥 Pre-merge checks | ✅ 7 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (7 passed)
Comment |
Antigravity review (Gemini via Ultra)This PR adds a self-hosted GitHub Actions workflow, style guide, and helper Bash scripts to run automated code reviews via the Antigravity CLI ( Blocking issues
Suggestions
Nitpicks
Automated first-pass review by |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/agy-review.md:
- Line 17: Update the Conventional Commits requirement in the commit-format
guidance to explicitly require <type>(<scope>): <subject>, including a mandatory
scope, an imperative subject, and a maximum subject length of 72 characters.
In @.github/workflows/antigravity-review.yml:
- Around line 20-23: Update the workflow permissions block to change
pull-requests access from write to read, while retaining contents: read and
issues: write for reading repository data and publishing comments.
- Around line 27-32: Restrict the workflow trigger in
.github/workflows/antigravity-review.yml lines 27-32 to trusted comment authors
and prevent fork PRs from reaching the privileged runner; in lines 34-49, check
out and execute only scripts from the trusted base ref, never the PR head. In
scripts/agy-review.sh lines 93-100, remove --dangerously-skip-permissions,
isolate the agy agent, and keep the publication token out of its process
environment.
- Line 35: Update the actions/checkout step in the workflow to use the immutable
commit pin 11d5960a326750d5838078e36cf38b85af677262 instead of the mutable `@v4`
reference.
In `@scripts/agy-review.sh`:
- Around line 155-159: Update the comment-cleanup pipeline in
scripts/agy-review.sh to resolve the authenticated automation account login,
then filter API results by both .user.login matching that login and .body
containing MARKER before deleting IDs. Preserve the existing pagination and
deletion behavior for comments owned by the automation.
- Around line 124-136: Update the SQLite fallback around the conversation
database lookup to persist and reuse the current agy conversation ID for this
invocation, then constrain each assistant-message query to that conversation
record rather than selecting the newest database and latest global row. If the
ID is unavailable or no matching record can be queried, fail closed without
writing unrelated conversation content to out_file.
- Around line 93-109: Update the agy invocation in the unbuffer execution path
to prevent untrusted PR diffs from reaching a tool-enabled, credentialed
session: either run it inside a disposable sandbox with no host credentials or
invoke it without tool access and without --dangerously-skip-permissions.
Preserve the existing prompt and output-capture behavior while ensuring
malicious diff content cannot access local files or post GitHub changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8e412793-2d34-4ca1-b977-6db0e2c9aa49
📒 Files selected for processing (4)
.github/agy-review.md.github/workflows/antigravity-review.ymlscripts/_agy_print.shscripts/agy-review.sh
| 4. Tests accompany behavior changes. | ||
|
|
||
| ## Conventions | ||
| - Conventional Commits (`feat|fix|docs|refactor|test|chore|perf|build|ci`). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Specify the mandated Conventional Commit format.
This permits scope-less messages and omits the imperative-subject and 72-character requirements. Require <type>(<scope>): <subject> explicitly.
As per coding guidelines, “Use Conventional Commits with <type>(<scope>): <subject>, an imperative subject of at most 72 characters”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/agy-review.md at line 17, Update the Conventional Commits
requirement in the commit-format guidance to explicitly require <type>(<scope>):
<subject>, including a mandatory scope, an imperative subject, and a maximum
subject length of 72 characters.
Source: Coding guidelines
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| issues: write |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Security Misconfiguration (CWE-250)
Reachability: External
Reduce pull-requests to read access.
The supplied script reads PR metadata/diffs but writes only issue comments. pull-requests: write unnecessarily expands the token available to this job; use pull-requests: read and retain issues: write only for comment publication.
As per path instructions, workflows must have least-privilege permissions: blocks.
🧰 Tools
🪛 zizmor (1.26.1)
[error] 22-22: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level
(excessive-permissions)
[error] 23-23: overly broad permissions (excessive-permissions): issues: write is overly broad at the workflow level
(excessive-permissions)
[warning] 22-22: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment
(undocumented-permissions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/antigravity-review.yml around lines 20 - 23, Update the
workflow permissions block to change pull-requests access from write to read,
while retaining contents: read and issues: write for reading repository data and
publishing comments.
Sources: Path instructions, Linters/SAST tools
| # PR opened/reopened, OR an `/agy-review` comment on a PR. | ||
| if: >- | ||
| github.event_name == 'pull_request' || | ||
| (github.event.issue.pull_request != null && | ||
| startsWith(github.event.comment.body, '/agy-review')) | ||
| runs-on: [self-hosted, agy] |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
LLM Security (CWE-74): Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
Reachability: External
Keep untrusted PR input out of the privileged runner and agent.
An external PR can trigger this job, alter the checked-out scripts that Lines 48-49 execute, and inject instructions through the raw diff. The same step exports GH_TOKEN and invokes agy with permission checks disabled.
.github/workflows/antigravity-review.yml#L27-L32: restrict comment-triggered reviews to trusted repository associations and do not dispatch untrusted fork PRs to theagyrunner..github/workflows/antigravity-review.yml#L34-L49: execute only scripts checked out from a trusted base ref; never execute PR-head scripts on this runner.scripts/agy-review.sh#L93-L100: remove--dangerously-skip-permissions; isolate the agent and keep the publication token outside its process environment.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 32-32: label "agy" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
📍 Affects 2 files
.github/workflows/antigravity-review.yml#L27-L32(this comment).github/workflows/antigravity-review.yml#L34-L49scripts/agy-review.sh#L93-L100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/antigravity-review.yml around lines 27 - 32, Restrict the
workflow trigger in .github/workflows/antigravity-review.yml lines 27-32 to
trusted comment authors and prevent fork PRs from reaching the privileged
runner; in lines 34-49, check out and execute only scripts from the trusted base
ref, never the PR head. In scripts/agy-review.sh lines 93-100, remove
--dangerously-skip-permissions, isolate the agy agent, and keep the publication
token out of its process environment.
| runs-on: [self-hosted, agy] | ||
| steps: | ||
| - name: Check out repo (for the style guide + scripts) | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
gh api repos/actions/checkout/git/ref/tags/v4 --jq '.object'Repository: doublegate/RustyN64
Length of output: 331
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
sha="$(gh api repos/actions/checkout/git/ref/tags/v4 --jq '.object.sha')"
gh api "repos/actions/checkout/commits/$sha" --jq '.sha'Repository: doublegate/RustyN64
Length of output: 198
Security Misconfiguration (CWE-829): Inclusion of Functionality from Untrusted Control Sphere
Reachability: External
Pin actions/checkout to 11d5960a326750d5838078e36cf38b85af677262. @v4 is a mutable ref; the workflow should use an immutable commit pin for third-party actions.
🧰 Tools
🪛 zizmor (1.26.1)
[error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/antigravity-review.yml at line 35, Update the
actions/checkout step in the workflow to use the immutable commit pin
11d5960a326750d5838078e36cf38b85af677262 instead of the mutable `@v4` reference.
Sources: Path instructions, Linters/SAST tools
| printf '\n--- UNIFIED DIFF ---\n' | ||
| cat "$diff_file" | ||
| } > "$prompt_file" | ||
|
|
||
| # --- run agy headless, under a PTY (works around agy issue #76: -p drops -------- | ||
| # stdout when stdout is not a TTY, e.g. piped/redirected/subprocess) --------- | ||
| flags=( --print-timeout "$AGY_PRINT_TIMEOUT" --sandbox --dangerously-skip-permissions ) | ||
| [ -n "$AGY_MODEL" ] && flags+=( --model "$AGY_MODEL" ) | ||
| [ -n "$AGY_EFFORT" ] && flags+=( --effort "$AGY_EFFORT" ) | ||
|
|
||
| out_file="$(mktemp)" | ||
| here="$(cd "$(dirname "$0")" && pwd)" | ||
| : > "$LOG" | ||
|
|
||
| if command -v unbuffer >/dev/null 2>&1; then | ||
| log "running agy via unbuffer (allocates a PTY)" | ||
| unbuffer "$AGY_BIN" "${flags[@]}" --print "$(cat "$prompt_file")" > "$out_file" 2>>"$LOG" || true |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Inspecting scripts/agy-review.sh and related references...\n'
wc -l scripts/agy-review.sh
sed -n '80,125p' scripts/agy-review.sh
printf '\n--- search references ---\n'
rg -n --hidden --no-ignore-vcs "dangerously-skip-permissions|AGY_BIN|agy-review\.sh|prompt_file|diff_file" .Repository: doublegate/RustyN64
Length of output: 4153
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Inspecting workflow and prompt framing...\n'
wc -l .github/workflows/antigravity-review.yml scripts/agy-review.sh
printf '\n--- workflow ---\n'
sed -n '1,220p' .github/workflows/antigravity-review.yml
printf '\n--- prompt framing ---\n'
sed -n '70,110p' scripts/agy-review.shRepository: doublegate/RustyN64
Length of output: 3766
LLM Security
Reachability: External
Do not feed untrusted diffs to a tool-enabled agent
This job runs on a self-hosted runner with the logged-in agy session and a write-scoped GITHUB_TOKEN (pull-requests: write, issues: write). Passing the raw PR diff into the prompt while --dangerously-skip-permissions is enabled gives a malicious PR a practical prompt-injection path to local files and to posted review output. Run the reviewer in a disposable sandbox with no host credentials, or remove tool access for this pass.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/agy-review.sh` around lines 93 - 109, Update the agy invocation in
the unbuffer execution path to prevent untrusted PR diffs from reaching a
tool-enabled, credentialed session: either run it inside a disposable sandbox
with no host credentials or invoke it without tool access and without
--dangerously-skip-permissions. Preserve the existing prompt and output-capture
behavior while ensuring malicious diff content cannot access local files or post
GitHub changes.
| if ! have_text "$out_file"; then | ||
| log "print output empty; trying SQLite conversation fallback" | ||
| if command -v sqlite3 >/dev/null 2>&1 && [ -d "$CONV_DIR" ]; then | ||
| db="$(ls -t "$CONV_DIR"/*.db 2>/dev/null | head -1 || true)" | ||
| if [ -n "${db:-}" ]; then | ||
| for q in \ | ||
| "SELECT text FROM messages WHERE role='assistant' ORDER BY rowid DESC LIMIT 1;" \ | ||
| "SELECT content FROM messages WHERE role='assistant' ORDER BY rowid DESC LIMIT 1;" \ | ||
| "SELECT body FROM message WHERE role='assistant' ORDER BY rowid DESC LIMIT 1;"; do | ||
| sqlite3 "$db" "$q" > "$out_file" 2>/dev/null && have_text "$out_file" && break | ||
| done | ||
| fi | ||
| fi |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
find "$HOME/.gemini/antigravity-cli/conversations" \
-maxdepth 1 -type f -name '*.db' -print0 |
while IFS= read -r -d '' db; do
printf '\n== %s ==\n' "$db"
sqlite3 "$db" '.schema'
doneRepository: doublegate/RustyN64
Length of output: 245
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Map the target file and related symbols.
ast-grep outline scripts/agy-review.sh --view expanded
printf '\n--- CONV_DIR / sqlite3 / invocation references ---\n'
rg -n "CONV_DIR|sqlite3|invocation|conversation|assistant|messages" scripts/agy-review.sh scripts -g '!**/*.md'Repository: doublegate/RustyN64
Length of output: 2337
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Read the relevant slice with line numbers once we know the file size.
wc -l scripts/agy-review.sh
sed -n '1,220p' scripts/agy-review.sh | cat -n | sed -n '100,170p'Repository: doublegate/RustyN64
Length of output: 3319
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
sed -n '1,220p' scripts/agy-review.sh | cat -n | sed -n '100,170p'
rg -n "CONV_DIR|sqlite3|invocation|conversation|assistant|messages" scripts/agy-review.sh scripts -g '!**/*.md'Repository: doublegate/RustyN64
Length of output: 5385
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
# Show the target file contents around the reported lines and locate any scoping logic.
nl -ba scripts/agy-review.sh | sed -n '110,150p'
printf '\n--- symbol search ---\n'
rg -n "CONV_DIR|invocation|conversation|messages|assistant|sqlite3" scripts/agy-review.shRepository: doublegate/RustyN64
Length of output: 197
Information Disclosure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor
Reachability: External
Scope the SQLite fallback to the current agy conversation. Picking the newest .db and latest assistant row is not tied to this run, so overlapping jobs can post another review’s content to the wrong PR. Persist the conversation ID for this invocation and query only that record; otherwise fail closed.
🧰 Tools
🪛 Shellcheck (0.11.0)
[info] 127-127: Use find instead of ls to better handle non-alphanumeric filenames.
(SC2012)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/agy-review.sh` around lines 124 - 136, Update the SQLite fallback
around the conversation database lookup to persist and reuse the current agy
conversation ID for this invocation, then constrain each assistant-message query
to that conversation record rather than selecting the newest database and latest
global row. If the ID is unavailable or no matching record can be queried, fail
closed without writing unrelated conversation content to out_file.
| gh api "repos/${REPO}/issues/${PR}/comments" --paginate \ | ||
| --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" 2>/dev/null \ | ||
| | while read -r cid; do | ||
| [ -n "$cid" ] && gh api -X DELETE "repos/${REPO}/issues/comments/${cid}" >/dev/null 2>&1 || true | ||
| done |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Delete only comments owned by this automation.
The marker is not an ownership check: any human or other bot comment containing it is selected for deletion. Resolve the authenticated bot login and filter on both .user.login and the marker.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/agy-review.sh` around lines 155 - 159, Update the comment-cleanup
pipeline in scripts/agy-review.sh to resolve the authenticated automation
account login, then filter API results by both .user.login matching that login
and .body containing MARKER before deleting IDs. Preserve the existing
pagination and deletion behavior for comments owned by the automation.
* fix(ci): harden the Antigravity reviewer per its own review of #49 The Antigravity (github-actions) review on #49 flagged real issues in the vendored reviewer scripts; I merged #49 without adjudicating them. This applies the safe, inspection-verifiable fixes to this repo's deployed copy: - SQLite conversation fallback is now opt-in (AGY_SQLITE_FALLBACK=1, default off) and only considers a db modified in the last two minutes. It read the newest .db from a shared per-user dir, which on a busy runner could surface an unrelated PR's or a private local conversation. The PTY path is the normal route, so defaulting the fallback off costs nothing in practice. - The pull_request PR-number lookup uses `// empty` + an explicit guard, so a missing field skips cleanly instead of passing the string "null" downstream. - `"$here"` is single-quoted inside the script(1) fallback command string, so a workspace path with spaces stays one token. Deferred (not fixed here): the primary path passes the prompt+diff via `--print "$(cat ...)"`, exposing it in the process table. The template's in-progress `_agy_pty.py` ("argv-safe") is the correct fix; this repo re-vendors it once that migration lands. See the PR for the full finding adjudication. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: re-vendor the updated Antigravity reviewer (concurrency + flock/retry) Replaces the manual stopgap hardening with the authoritative upstream template (~/Code/Local_Only-Projects/antigravity-pr-review), which now fixes the actual root cause of the "agy fails fast" review failures I diagnosed: - Job-level concurrency in the workflow, so an issue_comment run (e.g. a CodeRabbit comment landing mid-review) no longer cancels an in-progress pull_request review — the cancellation race that killed the #48/#50 runs. - agy is serialized across concurrent review jobs with flock, and the whole attempt retries on empty output — agy runs a singleton local server per user, so two --print calls at once collide and one reports the backend unavailable. The template is internally consistent again (all references resolve to _agy_print.sh). .gitignore untouched. This supersedes the earlier manual jq / SQLite / quoting fixes on this branch with the maintained versions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What this adds
The Antigravity PR reviewer — a self-hosted, Google AI Ultra-backed
auto-reviewer that replaces the retired Gemini Code Assist app. Installed from
the template at
~/Code/Local_Only-Projects/antigravity-pr-reviewviainstall-into-repo.sh --with-style-guide..github/workflows/antigravity-review.yml— triggers on PR open and on an/agy-reviewcomment; runs on the self-hosted runner labelledagy.scripts/agy-review.sh+scripts/_agy_print.sh— the reviewer (diff →adversarial prompt + style guide →
agyCLI → PR comment) and its PTY helper..github/agy-review.md— the starter review style guide.No repository secrets: the job posts with
GITHUB_TOKENand runs the model onthe machine's cached
agyOAuth session (Ultra rate limits, not a metered APIkey).
.gitignoreis intentionally untouched — the reviewer writes only to tempdirs.
Live test
Opening this PR fires the
openedtrigger from this branch, so this PR isits own end-to-end test — a "Antigravity review (Gemini via Ultra)" comment
should post within a minute or two now that the
agyrunner is online. Mergingto
mainis what enables auto-review-on-open and the/agy-reviewtrigger forevery future PR.
🤖 Generated with Claude Code