Automated pull request review agent for TensorFlow. The agent combines static analysis (pylint) with semantic review via Google ADK (google-adk) and Gemini (google-genai) to post structured, evidence-based code reviews on GitHub pull requests.
The TensorFlow PR Review Agent evaluates pull request diffs and modified files against the principle-based guidelines referenced in styleguide/tensorflow_pr_review.md. It submits a GitHub pull request review containing:
- A top-level review summary prefixed with the detected PR
CategoryandReason. - A GitHub pull request review
eventthat is always"COMMENT"(submit_pr_code_reviewinagent/agent.pyand the HTTP 422 fallback inpost_pull_request_reviewinagent/utils.py). While the model generatesoverall_assessmenttext ("No actionable review comments identified.","Minor improvements suggested.", or"Changes required."),summary_comment, andinline_comments, it never controls the GitHub review state (APPROVEorREQUEST_CHANGES). - Up to 5 line-anchored inline comments (
Priority 1throughPriority 3) with optional GitHubsuggestioncode blocks.
- Trigger:
.github/workflows/pr_review.ymlruns onpull_request_target(types: [labeled]) when the applied label isAgent Review. - Reaction Status (
eyes):clear_and_set_reactioninagent/main.pyremoves any existingeyesreactions posted bygithub-actions[bot]on the PR issue thread and posts aneyesreaction while processing. - PR Metadata & Diff Retrieval:
get_pull_request_detailsqueries the GitHub GraphQL API (with a REST API fallback) for the configuredPULL_REQUEST_NUMBERmetadata, up to 100 changed files, and the unified diff. The diff is annotated with explicit right-side ([L...]) and left-side ([LEFT L...]) line numbers and capped at 30,000 characters. - Labeled Commit Binding Check:
main.pycomparesPR_HEAD_SHA(github.event.pull_request.head.sha) against the live PRheadRefOid. IfPR_HEAD_SHAis missing/invalid or does not matchheadRefOid, the agent logs the SHA mismatch and aborts immediately before running Pylint, invoking Gemini, or posting a review. - Commit Idempotency Check:
has_agent_reviewed_commitchecks whether the verified commit SHA (verified_head_sha) already has a review authored bygithub-actions[bot]with a matchingcommit_idand HTML commit marker. If so, the agent skips analysis, posts arocketreaction, and exits. - Deterministic Categorization:
classify_pr_with_scoringclassifies the PR into one of 13 categories (Documentation,Test-only,Build / CI,TensorFlow Lite,XLA,Keras,API change,oneDNN / MKL,Bug fix,Feature,Refactor,Performance, orGeneral TensorFlow) using weighted rules over modified file paths, change types, diff patterns, and PR title/body keywords.get_focus_skip_areasmaps the category to specificFocus AreasandSkip Areas. - Pylint Static Analysis:
run_pylint_on_changed_filesruns once on modified Python files atverified_head_shaand collects diff-filtered diagnostics. - Semantic Review & Submission:
run_pr_reviewruns the ADKLlmAgent(InMemoryRunner) through a five-stage evaluation pipeline (Stage 0: PR Understanding,Stage 1: General Engineering,Stage 2: TensorFlow Repository Review,Stage 3: Category-Specific Review, andStage 4: Evidence & Quality Validation Gate) referencingstyleguide/tensorflow_pr_review.md, and callssubmit_pr_code_review_orchestratedto post a"COMMENT"review bound toPULL_REQUEST_NUMBERandverified_head_shato/repos/{OWNER}/{REPO}/pulls/{pr_number}/reviews. If GitHub returns422 Unprocessable Entityfor inline comment positions,post_pull_request_reviewfalls back to appending the inline comments into the top-level review body withevent="COMMENT". - Completion Reaction (
rocket): After verifying that the review forverified_head_shais recorded on GitHub, the agent clears theeyesreaction and posts arocketreaction.
- Scope: Runs only on non-deleted
.pyfiles modified by the PR (run_pylint_on_changed_filesinagent/utils.py). If no Python files were modified, Pylint is skipped. - Configuration: Uses
tensorflow/tools/ci_build/pylintrcif present, falling back to.pylintrcin the repository root. - Isolated Materialization: Each changed Python file's content at
head_shais retrieved viagit show <head_sha>:<path>(with a GitHub Contents API fallback atref=<head_sha>; whenhead_shais supplied, it never falls back to reading base-branch local files) and written into an isolatedtempfile.TemporaryDirectory(prefix="tf_pr_pylint_"). - Environment Isolation: The Pylint subprocess runs with
sys.executable -P -m pylint(shell=False),--option termination before file paths, and a minimal environment containing onlyPATHandHOME(excludingGITHUB_TOKEN,GEMINI_API_KEY, and workflow metadata variables). - Diff Filtering:
filter_pylint_output_by_diffretains only:- Diagnostics on right-side lines added or modified in the PR diff (
extract_modified_lines_by_file). - Fatal (
F*), syntax (E0001), or module-level (line 0) errors. unused-import(W0611) warnings where the unused symbol appears in lines deleted by the PR.
- Diagnostics on right-side lines added or modified in the PR diff (
- Limits & Resilience: Output is capped at 50 diagnostics and 5,000 characters. Pylint runs with a 120-second timeout; timeouts or execution errors are caught and reported as status text without aborting the review.
The agent constructs an LlmAgent with category context, Pylint evidence, and the guidelines loaded from styleguide/tensorflow_pr_review.md. agent/agent.py defines MODELS_POOL in priority order:
gemini-3.1-pro-previewgemini-3-flash-previewgemini-flash-latestgemini-3.1-flash-lite
During execution (agent/main.py):
- Pylint runs once before the model loop, and its output is reused across any fallback attempts.
is_fallback_eligible_errortriggers fallback to the next model inMODELS_POOLon model-availability errors (404/NOT_FOUND), rate-limit errors (429/RESOURCE_EXHAUSTED), or transient server errors (500,502,503/UNAVAILABLE,504/DEADLINE_EXCEEDED), as well as when a model finishes without a confirmed review on GitHub.- Non-fallback client/auth errors (
400,401,403) raise immediately without retrying additional models. - If all models in
MODELS_POOLfail,main.pyraises aRuntimeError.
- Marker Format: Every submitted review includes
commit_id: <verified_head_sha>in the API payload and appends an HTML comment marker to the review body:<!-- tensorflow-pr-review-agent: commit_sha=<verified_head_sha> --> - Same-Commit Idempotency: Before running Pylint or Gemini,
has_agent_reviewed_commitinspects/repos/{OWNER}/{REPO}/pulls/{pr_number}/reviews?per_page=100. A review counts only when(review.get("user") or {}).get("login") == "github-actions[bot]",commit_id == verified_head_sha, and the exact HTML marker is present inbody. - New-Commit Re-Review & Retry: Pushing a new commit updates
headRefOid; re-applying theAgent Reviewlabel triggers a fresh review for the new commit SHA. If a previous review submission failed, no marker is persisted on GitHub and the commit remains eligible for retry.
contents: read— Checkout the base repository and fetch the PR head commit object.pull-requests: write— Submit pull request reviews and inline review comments via/repos/{OWNER}/{REPO}/pulls/{pr_number}/reviews.issues: write— List existing reactions, delete staleeyesreactions, and posteyes/rocketstatus reactions on the PR issue thread (/repos/{OWNER}/{REPO}/issues/{pr_number}/reactionsand/repos/{OWNER}/{REPO}/issues/{pr_number}/reactions/{reaction_id}) viaclear_and_set_reaction. The active workflow does not create issue comments.
- The Gemini API key is stored in the GitHub Actions secret
API_NEEDS_REVIEWand injected into the workflow step as theGEMINI_API_KEYenvironment variable:GEMINI_API_KEY: ${{ secrets.API_NEEDS_REVIEW }}
- GitHub API authentication uses the workflow's
GITHUB_TOKEN(${{ secrets.GITHUB_TOKEN }}).
The workflow uses pull_request_target so it can access secrets.API_NEEDS_REVIEW and write PR reviews on fork PRs, while enforcing strict trust boundaries:
- Base Checkout Only:
actions/checkout(SHA-pinned withpersist-credentials: false) checks out the repository's base branch, running only the trusted code underpr_review_agent/. - No PR Branch Checkout or Execution: The workflow fetches the labeled commit SHA (
git fetch origin "$PR_HEAD_SHA" --depth=1) without checking out the PR worktree or executing PR code/tests. - Exact Labeled Commit Binding:
main.pyverifiesPR_HEAD_SHA == headRefOidbefore analysis and binds all downstream operations to that verified SHA. - Trusted PR Number & Review Event Enforcement: LLM-facing tools do not accept
pr_number, binding all operations to the trustedPULL_REQUEST_NUMBERenvironment variable, and always submit reviews withevent="COMMENT". - Path-Validated & Secret-Isolated Static Analysis:
_is_safe_relative_pathrejects absolute paths,..traversal, path components starting with-, and paths insidepr_review_agent/. Changed Python files are extracted as blobs atverified_head_shainto a temporary directory (is_relative_to-checked) and analyzed bypylintusing a minimalPATH/HOMEsubprocess environment.
Configured for Python 3.11 with packages listed in pr_review_agent/requirements.txt:
cd pr_review_agent
python -m pip install --upgrade pip
pip install -r requirements.txtagent/settings.py (using python-dotenv) and agent/main.py read the following environment variables:
GITHUB_TOKEN(required; raisesValueErrorif unset)GEMINI_API_KEY(populated fromsecrets.API_NEEDS_REVIEWin GitHub Actions)OWNER(repository owner, e.g.tensorflow)REPO(repository name, e.g.tensorflow)PULL_REQUEST_NUMBER(target PR number)PR_HEAD_SHA(expected PR head commit SHA from the triggeringpull_request_targetevent)PYTHONPATH(set to thepr_review_agentdirectory path)
From the pr_review_agent directory:
python -m agent.mainUnit tests are provided in pr_review_agent/test_idempotency.py and cover:
- Commit-level idempotency (
TestCommitIdempotency) - Diff line extraction and Pylint integration/filtering (
TestPylintIntegration) - Gemini model pool fallback behavior (
TestModelFallback) - Security hardening and regression checks (
TestSecurityHardening)
Run the test suite from pr_review_agent/:
python -m unittest test_idempotency.py