必须声明 permissions 中的 pull-requests: write,否则能读 PR 但静默发不出评论;不要用 pull_request_target 处理 fork 的 PR,会同时暴露写权限和密钥;panel 模式下每个成员需各自的密钥,且 OpenAI 目前不能作为第二个成员;max_tokens 低于约 4000 时大审查会被截断丢失
genai-code-review
ciroliniMIT活跃维护用 LLM 自动审查 Pull Request 的代码差异,并把评审意见直接评论到 PR 上的 GitHub Action,省去人工逐行看 diff 的功夫。
GitHub Action that reviews pull request diffs with an LLM and posts the comments on the PR
- uses: cirolini/genai-code-review@v3
with:
api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_pr_id: ${{ github.event.number }}README 未说明;这是 GitHub Actions 上运行的 Action,模型推理在所选 provider 侧完成,本地自托管端点(如 Ollama、vLLM)的硬件要求需自行满足
用 LLM 审查 PR diff 并发布行内评论的 GitHub Action,靠评论预算只发最值得看的几条
用 LLM 自动审查 Pull Request 的代码差异,并把评审意见直接评论到 PR 上的 GitHub Action,省去人工逐行看 diff 的功夫。
核心能力
- 按严重度排序,只发 max_comments 条(默认 5)
- 被压下的发现会在摘要中列出并说明如何放开
- 重复发现先过滤再排序,不占用评论预算
- 摘要说明未审查的文件、截断的 diff 等盲区
- 支持 OpenAI、Anthropic、Gemini 及 OpenAI 兼容端点
容易踩的坑
- v3.0.1v3.0.1 — fixes before the announcement最新正式版2026-09-21 · 2026-09-21 12:32 UTC
A fix release. Nothing new to configure; every input keeps its meaning. If you pin
@v3, you get this automatically.The summary no longer forgets what it did not review. The last reviewed commit advanced even when files were skipped as too large, the diff was truncated, or the model's output was unusable — so on the next push those files were neither reviewed nor listed under "Not reviewed". It now advances only after a complete run, and the summary says when the next push will review an earlier range again. Findings from a review GitHub rejected are no longer counted as posted: they are listed in the summary and retried.
Incremental review survives a merge from the base branch or a rebase. Both used to put changes from outside the pull request into the diff, and GitHub rejected the whole review with a 422. The comparison is checked first and falls back to reviewing the whole pull request, saying why.
One-click suggestions only where they apply. A finding moved a few lines to fit the diff now shows its suggestion as a plain code block, so "Commit suggestion" cannot overwrite code it was not written for.
Security:
.genai-review.ymlis read from the base branch. It was read from disk: with a checkout, the pull request controlled it —ignore_paths: ["**"]could review a change out of existence, andbase_urlcould send the API key elsewhere. Without a checkout, as recommended, it was never read at all. It is now fetched over the API from the base commit, andbase_urlis an action input only. If you setbase_urlin.genai-review.yml, move it to the workflow.The default
max_tokensis 8192 (was 2048). At 2048 a review of a large pull request truncated mid-JSON and was lost entirely.Also: the action is now named "GenAI Code Review" in
action.yml, and the docs no longer claim@v2was untouched — it points at the fix-only v2.1.Full list in the changelog.
- v3.0.0v3.0.0 — a code review bot with a comment budget2026-09-19 · 2026-09-19 18:54 UTC
查看更新说明
v3 rebuilds this action around a single idea: the scarce resource in code review is the reviewer's attention, and a bot that posts forty comments spends more of it than it returns.
Two changed defaults.
modenow defaults toreview, so a workflow that never setmodegets inline comments instead of one summary comment — pinmode: filesto keep the old output. Andprovidernow defaults togemini, though a workflow passing the deprecatedopenai_api_keywithout aproviderstays on OpenAI.Every v2 input still works, and
@v2itself is untouched. See the migration guide.Multiple providers
providerselects Google Gemini (the default), OpenAI, Anthropic, or any OpenAI-compatible server — Ollama, vLLM, Azure OpenAI, OpenRouter — viabase_url. New inputs:provider,model,api_key,base_url,temperature,max_tokens. Theopenai_*inputs remain as aliases.Transient failures are retried with backoff; permanent ones are not, because retrying a rejected key only delays the message you need. A provider failure is now reported in the pull request and fails the check, rather than surfacing only in a log — a review that silently did not happen looks exactly like a review that found nothing.
Inline comments on the right lines
With
mode: review, the model returns findings matching a JSON schema — file, line range, severity, category, confidence, title, rationale, optional suggestion — validated in code, with one repair attempt when it does not match. Findings are posted as inline comments grouped into a single review.Review pressure controls
max_comments(default 5), ranked by severity then confidence. Suppressed findings are counted and listed, never dropped.min_severityandmin_confidence.- One sticky summary comment, edited in place instead of a new one per push.
- Incremental review: on a push, only the new commits are reviewed, and findings already raised are not repeated.
ignore_paths, defaulting to lockfiles, generated and vendored code, snapshots and minified assets.- Large diffs are chunked, and anything that did not fit is reported rather than silently truncated.
panel(experimental): two providers merged, with agreement as a confidence signal. Roughly double the cost.
Measured
Model Precision Recall Noise rate Cost / PR gemini-3.8-flash100% 93% 0% $0.0009 gpt-oss-120b70% 93% 83% n/a 21 fixtures, 15 with a seeded defect and 6 clean. Recall did not separate the two models; noise did. Full numbers and limits in
docs/results/.Measurement
Every run writes a JSON log — provider, model, tokens, estimated cost, latency, findings by severity, posted versus suppressed, coverage — containing no credentials, no diff and no source code.
evals/holds 21 fixtures with seeded, labelled defects plus clean diffs, andmake evalscores precision, recall, noise rate, cost per pull request and latency.Fixed
Everything in v2.1: the container entrypoint path (#47), the unpinned
httpxthat broke client construction, and the deprecated default model. Also the patch parser, which split on the bare substringdiffand broke on any diff that mentioned the word (#28).Security
The diff is treated as untrusted input: fenced with a per-request random sentinel, with instructions placed after the data, and the model told that injection attempts are themselves reportable. Use
pull_request, notpull_request_target, for pull requests from forks.
Quick start
on: pull_request: types: [opened, synchronize] permissions: contents: read pull-requests: write jobs: review: runs-on: ubuntu-latest steps: - uses: cirolini/genai-code-review@v3 with: api_key: $ - v2.1v2.1 — unbreak the action2026-09-19 · 2026-09-19 17:35 UTC
查看更新说明
A maintenance release. No new features and no input changes:
v2.1exists becausev2had stopped working, in two independent ways.Fixed
-
The action could not start on any repository but this one (#47). GitHub runs container actions with
--workdir /github/workspace, which overrides the image'sWORKDIR, so the relativeCMD ["python", "src/main.py"]resolved against the caller's checked-out repository and failed withcan't open file '/github/workspace/src/main.py'. The entrypoint is now absolute. Reported by @Asthethi; fix from @arjunsuresh in #50.This repository's own workflow checked the repository out before running the action, which put a
src/main.pyon the container's working directory and made the action appear to work here while it failed everywhere else. That checkout has been removed, and CI now builds the image and runs it the way GitHub does. -
The OpenAI client could not be constructed.
openaiwas pinned to1.30.1buthttpxwas not pinned at all.httpx0.28 removed theproxiesargument thatopenai1.30 passes, so a fresh build failed withClient.__init__() got an unexpected keyword argument 'proxies'. All dependencies, including transitive ones that affect startup, are now pinned, and the Python base image is pinned to a patch version.
Changed
-
Default model is now
gpt-5.6-luna(wasgpt-3.5-turbo, which is deprecated and loses API access on 2026-10-23).gpt-5.6-lunais cheaper thangpt-3.5-turboon both input and output tokens, so this does not raise the cost of an existing workflow. Passopenai_modelto choose a different one. -
Dependencies:
openai1.30.1 → 1.109.1,PyGithub2.3.0 → 2.10.0,requests2.31.0 → 2.34.2,httpxpinned at 0.28.1.
Documentation
- The workflow example now includes the required
permissionsblock. Missingpull-requests: writewas a recurring cause of "it runs but posts nothing". - The configuration reference documented an
openai_engineinput that the action never read. The correct name isopenai_model. - Removed
actions/checkoutfrom the example. This action reads the pull request over the API and does not need the repository on disk.
Upgrading
None required.
v2now points at this release, and every existing input keeps its previous meaning. If you pinnedopenai_model: "gpt-3.5-turbo"explicitly, change it before 2026-10-23. -
- v2Enhance Code Review Process with Custom Prompts, Language Selection, and Automated Testing2024-05-21 · 2024-05-21 16:53 UTC
查看更新说明
Overview:
This PR introduces significant enhancements to the code review automation process, including support for custom prompts, language selection, and integration of automated testing using pytest and pylint.
Changes:
Custom Prompts:
- Added support for user-defined custom prompts.
- Example usage: custom_prompt: "Give a rating from 1 to 10 for this code:".
Language Selection:
- ntroduced the ability to specify the language for code review responses.
- Default language is set to English (en).
Automated Testing:
- Implemented unit tests using pytest for core functionalities.
Refactoring:
- Modularized the code for better maintainability.
- Improved error handling and logging.
- Ensured compliance with PEP8 standards.
- v1.4Code Review with ChatGPT2023-12-05 · 2023-12-05 00:36 UTC
查看更新说明
Now we do a code review.
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
GenAI Code Review
A GitHub Action that reviews pull request diffs with an LLM and posts inline comments. It works with OpenAI, Anthropic, Google Gemini, or any OpenAI-compatible server.
Unlike most such tools, it is built around a comment budget: it posts only the few findings most worth a human's attention, and tells you what it held back.
Quick start
One workflow file. Nothing else.
# .github/workflows/code-review.yml
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: cirolini/genai-code-review@v3
with:
api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_pr_id: ${{ github.event.number }}
No actions/checkout step is needed — the action reads the pull request over
the API and never uses the repository on disk.
The permissions block is required. Without pull-requests: write the action
authenticates, reads your pull request, and then silently fails to post.
Why a comment budget
AI made writing code cheap. It did not make reviewing it cheap. The bottleneck moved from the person writing to the person reading, and that person's attention is now the scarce resource on the team.
A review bot that posts forty comments does not add forty units of value. It spends forty units of somebody's attention, and it usually returns less than the best five comments would have. Worse, it trains the reviewer to skim past the bot entirely — at which point the two comments that actually mattered are lost along with the thirty-eight that did not.
So this one is given a budget and made to choose:
max_comments(default 5) — findings are ranked by severity, with confidence ordering within a severity, and only the top few are posted.- Nothing is silently dropped. The summary says how many findings were held back, lists them, and names the setting to change to see them. A reviewer who knows twelve minor findings were suppressed can ask for them; one who was never told has been quietly misled about coverage.
- Repeat findings do not consume budget. A finding already raised on an earlier push is filtered out before ranking, so it cannot push a new finding out of the budget.
- The summary says what was not reviewed — ignored files, files too large for one request, truncated diffs, and the fact that an incremental run only looked at recent commits. This is the section most tools leave out, and it is the one that decides whether silence from the bot can be trusted.
The same idea drives the rest of the design: one sticky summary comment edited in place rather than a new one per push, one grouped review rather than N separate notifications, and lockfiles and generated code dropped before they ever reach the model.
Whether this actually produces better reviews is a measurable question, not a
claim. See docs/results/ for the harness and
docs/case-study.md for the reasoning.
Providers
provider | Default model | Key read from |
|---|---|---|
gemini (default) | gemini-3.8-flash | api_key, else GEMINI_API_KEY |
openai | gpt-5.6-luna | api_key, else OPENAI_API_KEY |
anthropic | claude-sonnet-5 | api_key, else ANTHROPIC_API_KEY |
openai-compatible | none — model is required | api_key, optional |
Defaults are cost-conscious rather than each vendor's strongest model, because
this runs on every push to every pull request and the bill is yours. Set model
to pick a different one.
gemini is the default because it is the configuration with measured numbers
behind it: 100% precision and 0% noise on the eval set, at $0.0009 per pull
request. See docs/results/.
A workflow that passes the deprecated openai_api_key and does not set
provider stays on OpenAI, so moving the default cannot send an OpenAI key to
Google.
- uses: cirolini/genai-code-review@v3
with:
provider: anthropic
api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_pr_id: ${{ github.event.number }}
Panel mode (experimental, off by default)
panel runs two providers over the same diff and merges what they found. A
finding both reported has its confidence raised by 15%; one only a single
member reported has it cut by 30%, so it is demoted rather than discarded — a
model spotting a real blocker the other missed is still worth keeping. Findings
are matched across members by file, category and normalised title, because two
models describing one defect rarely agree on the line or the wording. The
summary reports the agreement rate.
- uses: cirolini/genai-code-review@v3
with:
panel: gemini,anthropic
api_key: ${{ secrets.GEMINI_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
github_pr_id: ${{ github.event.number }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Every member needs its own key. The api_key input goes to the first
member only; the others read their provider's environment variable
(GEMINI_API_KEY, ANTHROPIC_API_KEY). Without it the run fails at startup
with provider ... needs an API key rather than quietly reviewing with one
model.
OpenAI cannot currently be the second member. The action declares
OPENAI_API_KEY from its own deprecated input, so a value passed through the
step's env: is overwritten with an empty one and the member fails to build.
Put OpenAI first, as provider/api_key, and the other model second.
The price is roughly double — two requests, two sets of tokens, and the
latency of both. That is why it is off by default and called experimental:
there is no measurement yet showing the second opinion is worth what it costs.
Panel mode has never been run against two live providers. See
docs/results/.
Self-hosted and third-party endpoints
openai-compatible covers Ollama, vLLM, Azure OpenAI, OpenRouter, Together and
anything else exposing the same API. api_key is optional, so a local server
with no auth works:
provider: openai-compatible
base_url: http://localhost:11434/v1
model: llama3
Configuration
Every setting can be given as an action input or in a .genai-review.yml file
at the repository root. Action inputs take precedence over the file, so a
workflow can always override the repository's defaults.
The file is read over the API from the pull request's base branch, not from
the pull request, and needs no checkout. A change to it takes effect once it is
merged. Otherwise whoever opens a pull request could set ignore_paths: ["**"]
and review their own change out of existence.
See .genai-review.yml.example for a commented
file. There is deliberately no api_key key in it: credentials belong in a
secret. base_url is also input-only, because it decides which server receives
the key.
Reference
| Input | Default | What it does |
|---|---|---|
github_token | — | Required. Usually ${{ secrets.GITHUB_TOKEN }} |
github_pr_id | — | Required. Usually ${{ github.event.number }} |
api_key | — | Key for the chosen provider |
provider | gemini | gemini, openai, anthropic, openai-compatible |
model | per provider | Model ID |
base_url | — | Required for openai-compatible |
mode | review | review posts inline comments; files/patch are v2 behaviour |
max_comments | 5 | Inline comments per review; the rest are counted and listed |
min_severity | nit | blocker, major, minor, nit |
min_confidence | 0 | 0 to 1 |
ignore_paths | see below | Globs to skip. Unset uses the defaults |
incremental | true | On a push, review only the new commits |
panel | — | Experimental. Two providers, merged. ~2x cost — details |
language | en | Language the findings are written in |
custom_prompt | — | Extra instructions for the reviewer |
temperature | 0.5 | Sampling temperature |
max_tokens | 8192 | Response length cap. Below ~4000 a large review truncates and is lost |
config_path | .genai-review.yml | Repository config file, read from the base branch |
runlog_path | genai-review-runlog.json | Where the per-run JSON log is written |
ignore_paths defaults to skipping lockfiles, vendored and generated code,
snapshots, migrations and minified assets — files nobody reviews by hand. Set it
to a single space to review everything.
Deprecated inputs
openai_api_key, openai_model, openai_temperature and openai_max_tokens
still work and are used whenever their v3 equivalent is unset. A workflow
written for v2 runs on v3 unchanged. See the
migration guide.
Security
Permissions. The action needs contents: read and pull-requests: write
and nothing more.
Do not use pull_request_target for pull requests from forks. That trigger
runs with a token that can write to your repository and with access to your
secrets, while the code being reviewed is controlled by whoever opened the pull
request. The ordinary pull_request trigger is correct here: forks get a
read-only token and no secrets, which is exactly what you want when the input is
untrusted. If reviews on fork pull requests matter to you, accept that they will
not run rather than reaching for pull_request_target.
The diff is treated as untrusted data. Code under review can contain text
aimed at the reviewer — "ignore previous instructions", a forged system message,
a comment claiming the change is pre-approved. The diff is fenced with a
randomly generated per-request sentinel it cannot forge, the real instructions
come after the data, and the model is told that an injection attempt is itself
worth reporting as a security finding. The bot never executes anything from a
diff; its only output is findings validated against a schema.
API keys. Pass them from a secret. The key is never written to the run log,
never included in an error message posted to a pull request, and never read from
.genai-review.yml. Neither is base_url, the server the key is sent to.
The config file cannot be changed by the pull request it configures.
.genai-review.yml is read from the base branch, so a pull request that edits it
is reviewed under the settings already merged.
What leaves your repository. The diff of the files being reviewed is sent to
whichever provider you configure. If that is not acceptable for a given
repository, use ignore_paths to exclude the sensitive parts, or point
provider: openai-compatible at a model you host yourself.
Run logs
Every review writes a JSON file with provider, model, tokens, estimated cost, latency, findings by severity, posted versus suppressed, and what was not reviewed. Upload it to keep a record:
- uses: actions/upload-artifact@v4
if: always()
with:
name: genai-review-runlog
path: genai-review-runlog.json
It contains no credentials, no diff and no source code.
Did anyone act on the comments?
The run log records what the bot said. To see what reviewers did with it — reactions, replies and whether the thread was resolved — read a repository the action has already run on:
make feedback REPO=owner/name # needs $GITHUB_TOKEN with read access
It is read-only and calls no model. The signals are reported side by side rather than averaged into one score: a resolved thread means the conversation ended, which happens both when a finding is fixed and when it is dismissed. The row that carries the most information is the share of comments nobody touched at all.
How it works
- Files matching
ignore_pathsare dropped before anything is sent. - On a push, only the commits since the last review are considered.
- The remaining diff is chunked to fit the model's context, and anything that does not fit is reported rather than silently truncated.
- The model returns findings matching a JSON schema — file, line range, severity, category, confidence, title, rationale, optional suggestion. Output that does not validate gets exactly one repair attempt.
- Findings are checked against the diff. GitHub rejects an entire review if one comment points outside the diff, so findings that cannot be placed are reported in the summary instead of taking the review down with them.
- The top
max_commentsare posted as one grouped review, and a single sticky summary comment is created or updated.
Development
make install # dependencies
make check # ruff + pytest
make eval-dry # list the eval fixtures without calling a provider
make eval # run the eval set (needs an API key; costs money)
make feedback # acceptance signal from a repository the action has run on
Related work
dlidstrom/genai-code-review
is a fork that kept this action working for people while this repository sat
unmaintained for two years, and published releases under its own v3.0.x tags.
If you are using it today, it still works. That fork and this one are not
version-aligned; the tags here are independent of the ones there.
License
MIT — see LICENSE.
Authors
- Rafael Cirolini — cirolini
- Glauber Borges — glauberborges