Automated PR code review powered by AI — supports GitHub Models, GitHub Copilot, OpenAI, Anthropic, Google Gemini, xAI, OpenRouter, Ollama, Azure, AWS, and more.
Zero-config setup using GitHub Models — works with GITHUB_TOKEN, no extra secrets needed:
name: AI Code Review on: pull_request: types: [opened, synchronize, reopened] permissions: pull-requests: write contents: read models: read jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} api_key: ${{ secrets.GITHUB_TOKEN }}
Trigger the review manually from the Actions tab and choose the provider, model, API key, and PR number yourself:
name: AI Code Review on: pull_request: types: [opened, synchronize, reopened] workflow_dispatch: inputs: pr_number: description: "Pull request number to review" required: true type: string provider: description: "AI provider" required: false type: choice default: "github-azure-models" options: - github-azure-models - github-models - copilot - openai - anthropic - openrouter - ollama - xai - zai - google - azure - aws - custom model: description: "AI model name (e.g. gpt-4o, openai/gpt-4o-mini, claude-3-5-sonnet-20241022)" required: false default: "gpt-4o" type: string api_key: description: "AI API key (leave empty to use GITHUB_TOKEN)" required: false default: "" type: string files_changed: description: "Post inline review comments on changed files (Files changed tab)" required: false default: false type: boolean username: description: "Review only changes by this GitHub username ('all' = everyone, empty = you)" required: false default: "" type: string mode: description: "Model mode (thinking enables reasoning mode)" required: false default: "normal" type: choice options: - normal - thinking effort: description: "Reasoning effort (none = don't send)" required: false default: "none" type: choice options: - none - low - medium - high - max permissions: pull-requests: write contents: read models: read jobs: review: runs-on: ubuntu-latest steps: - name: Mask API key in logs if: ${{ inputs.api_key != '' }} run: | key=$(jq -r '.inputs.api_key // empty' "$GITHUB_EVENT_PATH") if [ -n "$key" ]; then echo "::add-mask::$key" fi - uses: actions/checkout@v4 - uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.inputs.pr_number || github.event.pull_request.number }} provider: ${{ github.event.inputs.provider || 'github-azure-models' }} model: ${{ github.event.inputs.model || 'gpt-4o' }} api_key: ${{ github.event.inputs.api_key || secrets.GITHUB_TOKEN }} files_changed: ${{ github.event.inputs.files_changed || 'false' }} username: ${{ github.event.inputs.username || github.actor }} mode: ${{ github.event.inputs.mode || 'normal' }} effort: ${{ inputs.effort != 'none' && inputs.effort || '' }}
With this setup the workflow runs automatically on PR activity, and you can also go to Actions → AI Code Review → Run workflow to review any PR on demand with any provider/model. Enable files_changed to have the AI leave inline comments on the changed lines instead of one combined comment, and use username to scope the review to a single author's changes (all = everyone, leave empty = the user who runs the workflow).
To make the workflow manual-only (no automatic run when a PR is created), remove the pull_request trigger and keep only workflow_dispatch — inputs can then be referenced directly as ${{ inputs.<name> }}.
⚠️ Security note: GitHub has no secret/password input type forworkflow_dispatch— the form field is plain text, and a manually entered key stays visible in the run's event payload. The::add-mask::step above masks the key as***in the run logs (it reads the key from$GITHUB_EVENT_PATHat runtime, because values passed viaenv:are printed in the step's log header before masking takes effect). Prefer leavingapi_keyempty (falls back toGITHUB_TOKEN) or wiring it to a repository secret (e.g.secrets.OPENAI_API_KEY) instead of pasting a raw key.
Set memory: true so the AI doesn't start from scratch every run. Two layers:
1. Dated memory files (context_path, default .github/ai-review/)
Both file types use a fixed schema (stable headings — content is free-form):
context.md — stable base knowledge (human-edited), always loaded:
# AI Review Context ## Architecture # how the codebase is built ## Skip Review # files/patterns not to review (generated output, lockfiles, ...) ## Conventions # team rules the review should apply ## Known Issues # accepted issues — do not re-report
<date>-context.md — daily memory written by the AI after each run. Every run appends a section with the exact same structure (enforced by code — missing sections become - none):
# AI Review Memory — 2026-10-28 ## Run 2026-10-28 06:17 UTC — PR #5 ### Patterns # recurring issues/patterns observed ### Decisions # conventions/decisions applied or agreed ### Follow-ups # open items to check next time ### Fixed # previously reported items this diff resolves
- Before reviewing:
context.md+ the latest<date>-context.mdfiles are injected into every agent's prompt as prior knowledge ("don't re-report known/accepted issues"). - After each run: the AI summarizes what's worth remembering (recurring patterns, decisions, follow-ups) and the action commits it into today's
<date>-context.md(multiple runs the same day append sections). Commits carry[skip ci]. - Requires
contents: writepermission in the calling workflow; if the save fails (e.g. read-only token), the review still completes — memory saving is best-effort.
2. Previous feedback on the PR — the bot's own earlier comments are loaded so it doesn't re-report the same issues; fixed items get a short ✅ acknowledgment instead.
Skills are knowledge modules injected into every agent's system prompt when enabled — each one focuses the review on a specific area.
Built-in skills live in the action's skills/ folder — one folder per skill:
skills/
├── security/SKILL.md
├── testing/SKILL.md
├── performance/SKILL.md
├── clean-code/SKILL.md
├── error-handling/SKILL.md
└── api-contract/SKILL.md
| Skill | Focus |
|---|---|
security |
Injection, XSS, auth checks, IDOR, secrets, input validation, SSRF |
testing |
Coverage of changed behavior, edge cases, meaningful assertions, deterministic tests |
performance |
N+1 queries, missing indexes, unbounded queries, O(n²) hot paths, caching |
clean-code |
Naming, small functions, guard clauses, DRY, dead code, magic numbers |
error-handling |
No silent swallowing, timeouts, retries, resource cleanup |
api-contract |
REST conventions, breaking changes, payload validation, versioning |
Selecting skills — the skills input:
auto— picked from the diff content (always security/clean-code/error-handling/testing; addsperformancefor query/loop-heavy diffs andapi-contractwhen routes/handlers change)all— every registered skill- comma-separated names, e.g.
security,performance— includes custom skills - empty (default) — disabled
Adding / importing your own skills:
# 1) Inline JSON custom_skills: | [{"name": "k8s", "description": "Kubernetes review", "content": "- Check resource limits...\n- Check liveness/readiness probes"}] # 2) Import a skills folder from your repo skills_path: .github/skills
Folder format — one folder per skill, each with a SKILL.md using frontmatter:
.github/skills/
└── review-security/
└── SKILL.md
--- name: review-security description: "Review security-sensitive changes... Use for code review involving credentials or network changes." --- # Review security-sensitive changes 1. Identify trust boundaries and trace untrusted input...
The folder is scanned recursively — every *.md file at any depth becomes a skill:
<name>/SKILL.md→ skill id = folder name (or frontmattername)- any other
<name>.mdat any depth (**/*.md) → skill id = file name (or frontmattername)
The frontmatter name overrides the default id; description is shown with the skill in the prompt. Later definitions override earlier ones with the same name.
By default the review runs as one combined agent — a single AI call whose persona merges all four responsibilities (author self-review + explanation, primary logic/edge-case/test checks, specialist security/architecture/DB checks, and a closing checklist table).
Agent definitions live in markdown files — one per agent — in the action's agents/ folder:
agents/
├── author.agent.md # 🧑💻 Author (Self-Review)
├── primary.agent.md # 🔍 Primary Reviewer
├── specialist.agent.md # 🛡️ Specialist Reviewer
├── checklist.agent.md # 📋 Code Review Checklist
└── combined.agent.md # 🤖 default single agent (all four roles)
Each <name>.agent.md file uses frontmatter + markdown body (the file name is the agent id you reference in agents):
--- name: "🔍 Primary Reviewer" description: "Peer review of business logic, edge cases, error handling, and tests." tools: [read, search] --- You are the Primary Reviewer, a peer developer on the same team. ...
name— display label shown in the reportdescription/tools— declarative metadata for humans- body — the agent persona
To customize, fork the action and edit these files — or keep your own folder in your repo and point agents_path at it (e.g. agents_path: .github/agents). Same file name overrides a built-in; a new file name adds a new agent you can select in agents. Legacy <name>.md files with a first-line # Label are still accepted.
Set agents: all (or a comma-separated subset) to run the agents in parallel — each posts its own section (or prefixed inline comments in files_changed mode). Use this when you want deeper, role-focused passes and don't mind the extra AI calls.
| Agent | Role | Focus |
|---|---|---|
author |
🧑💻 Author (Self-Review) | Self-reviews their own code: explains how the code works (what each part does and why) and flags leftover debug code — plus a deterministic PR size check against the 200–400 changed-lines guideline |
primary |
🔍 Primary Reviewer | Business logic and correctness, edge cases, error handling, readability, and whether unit/integration tests cover the real behavior |
specialist |
🛡️ Specialist Reviewer | System architecture, scalability, overall impact, security vulnerabilities (injection, auth, secrets), and database performance in critical paths — holds final approval for core/production systems |
checklist |
📋 Code Review Checklist | Walks through a standard 12-item review checklist (correctness, readability, error handling, edge cases, security, performance, tests, docs, debug leftovers, compatibility, dependencies, logging) and reports ✅/files_changed mode it summarizes as a table in the Conversation tab instead of inline comments |
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} api_key: ${{ secrets.GITHUB_TOKEN }} agents: all # or e.g. "primary,specialist" files_changed: true # optional: inline comments prefixed with the agent name
Note: each agent is a separate AI call per diff (or per file in
files_changedmode), so runtime and token usage grow with the number of agents.
Use custom_agents (a JSON array) to override a built-in agent or add your own agent:
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} api_key: ${{ secrets.GITHUB_TOKEN }} agents: primary,db # run the (overridden) primary + the custom "db" agent custom_agents: | [ { "name": "primary", "persona": "You are a strict Primary Reviewer for a fintech team. Check business logic, edge cases, error handling, readability, and test coverage. Flag anything that could affect money movement." }, { "name": "db", "label": "🗄️ DBA Reviewer", "persona": "You are a DBA reviewing this change. Check schema changes, query performance (N+1, missing indexes), transaction usage, and migration safety." } ]
Rules:
namematching a built-in (author,primary,specialist,checklist) → overrides itspersona/label; anything you don't set keeps the default- New
name→ adds a new agent;labelis optional (defaults to the name),personais the agent's role prompt - Custom names can be used in the
agentslist, andagents: allruns built-ins plus customs - If
agentsis empty, only the new custom agents run (overrides alone need the agent to be selected) - Every agent (built-in or custom) supports
web_search,mode/effort, and both review modes
Set web_search: true to let every agent look things up on the web while reviewing (docs, APIs, CVEs, best practices). How it works depends on the API type:
api_type |
Mechanism |
|---|---|
messages (Anthropic) |
Native server-side web_search tool — the model searches by itself |
responses (OpenAI) |
Native web_search tool |
chat / GitHub Models |
Function-calling loop: the model calls a web_search tool and the action searches DuckDuckGo (no API key needed, up to 3 rounds) and feeds results back |
Requires a model/provider that supports tools — providers without tool support may reject the request. Combined with
agents: all, each agent can search independently.
| Input | Required | Default | Description |
|---|---|---|---|
gh_pat |
✅ | — | GitHub PAT for GitHub API (fetching diff, posting comments) |
repo |
✅ | — | Repository in owner/repo format |
pr_number |
✅ | — | Pull request number to review |
api_key |
✅ | — | API key for the provider. For github-models: use GITHUB_TOKEN. For copilot: use a personal PAT with Copilot subscription |
provider |
github-azure-models |
AI provider shorthand (see Providers) | |
model |
gpt-4o |
Model name (see Providers for naming convention per provider) | |
title |
AI Code Review |
Title shown in the PR comment header | |
api_url |
— | Full endpoint URL — overrides provider + api_type defaults | |
api_type |
chat |
API format: chat, responses, or messages |
|
max_tokens |
2048 |
Maximum tokens in AI response | |
max_diff_chars |
10000 |
Maximum characters of diff sent to AI | |
language |
English |
Language for the review response | |
files_changed |
false |
true = AI reviews each changed file and posts inline review comments on the diff (Files changed tab) instead of one combined PR comment |
|
max_comments |
unlimited | Max inline review comments (files_changed mode) — 0/empty = no limit and every comment is posted; set a number to cap with round-robin distribution across agents |
|
username |
run actor | GitHub username whose changes to review — only files changed in commits authored by this user. Agents talk to the owner directly: every inline comment starts with the owner's username as a clickable profile link (no mention noise), and the review summary carries the real @mention | |
mode |
normal |
thinking = enable thinking/reasoning mode where supported (enable_thinking for OpenAI-compatible chat, thinking block for Anthropic messages). normal sends nothing extra |
|
effort |
— | Reasoning effort: low, medium, high, max — adds reasoning_effort (chat), reasoning.effort (responses), or maps to the thinking budget (messages). Empty or none sends nothing |
|
agents |
— | Empty (default) = one combined agent covering all four roles in a single AI call. Set all or a comma-separated list to split the work into separate agents (see Multi-Agent Review) |
|
agents_path |
— | Load/override agents from a folder of <agent-name>.md files in your repo (e.g. .github/agents) |
|
custom_agents |
— | JSON array to override built-in agents or add new ones (see Custom Agents) | |
skills |
— | Skills injected into every agent's prompt: auto, all, comma-separated names, or empty (see Skills) |
|
custom_skills |
— | Add your own skills as a JSON array (see Skills) | |
skills_path |
— | Import skills from a folder of *.md files in the repo, e.g. .github/ai-skills |
|
web_search |
false |
true = let the AI search the web during review (native tool for Anthropic messages / OpenAI responses; function-calling loop via DuckDuckGo for chat / GitHub Models — model must support tools) |
|
memory |
false |
true = enable review memory: loads context.md + latest <date>-context.md files and the bot's previous comments on the PR (no repeat reports), then saves an AI-generated memory file for today back to the repo (needs contents: write) |
|
context_path |
.github/ai-review |
Folder holding context.md (stable base) and <date>-context.md daily memory files |
|
memory_branch |
default branch | Branch to save memory files to (must already exist; saving is skipped with a warning if not found) | |
memory_files |
3 |
Which dated memory files to load: a number = latest N, specific dates (2026-10-25,2026-10-28), or a range (2026-10-20..2026-10-28) |
provider |
api_type default |
Base URL | Auth | Model naming |
|---|---|---|---|---|
github-azure-models (default) |
chat |
https://models.inference.ai.azure.com |
GITHUB_TOKEN ✅ |
Plain name, e.g. gpt-4o |
github-models |
chat |
https://models.github.ai/inference |
GITHUB_TOKEN ✅ |
Publisher-prefixed, e.g. openai/gpt-4o-mini |
copilot |
chat |
https://api.githubcopilot.com |
Personal PAT with Copilot subscription | Plain name, e.g. gpt-4o |
openai |
chat |
https://api.openai.com/v1 |
API key | Plain name, e.g. gpt-4o |
anthropic |
messages |
https://api.anthropic.com/v1 |
API key | e.g. claude-3-5-sonnet-20241022 |
openrouter |
chat |
https://openrouter.ai/api/v1 |
API key | e.g. meta-llama/llama-3.3-70b-instruct |
ollama |
chat |
http://localhost:11434/v1 |
— | e.g. llama3 |
xai |
chat |
https://api.x.ai/v1 |
API key (Grok) | e.g. grok-3 |
zai |
chat |
https://api.z.ai/api/coding/paas/v4 |
API key (GLM) | provider-specific |
qwencloud |
chat |
https://ap-southeast-1.maas.qwencloudapi.com/compatible-mode/v1 |
API key (Qwen Cloud) | e.g. qwen-plus, qwen-max |
qwencloud-plan |
chat |
https://token-plan.maas.qwencloudapi.com/compatible-mode/v1 |
API key (Qwen Cloud plan) | e.g. qwen-plus, qwen-max |
google |
chat |
https://generativelanguage.googleapis.com/v1beta/openai |
API key | e.g. gemini-2.0-flash |
azure |
chat |
api_url |
api-key header |
your deployment name |
aws |
chat |
api_url |
API key | provider-specific |
custom |
chat |
api_url |
API key | provider-specific |
Note:
copilotprovider exchanges a personal PAT for a short-lived session token automatically.GITHUB_TOKENfrom GitHub Actions will not work for Copilot — usegithub-azure-models/github-modelsinstead for zero-config setups.Qwen Cloud has two shorthands:
qwencloud(regional endpointap-southeast-1.maas.qwencloudapi.com) andqwencloud-plan(token-plan endpointtoken-plan.maas.qwencloudapi.com). Both default to the OpenAI-compatible endpoint — setapi_type: messagesto use their Anthropic-compatible endpoint instead (…/apps/anthropic/messages).GitHub Models has two provider shorthands:
github-azure-models(default) uses the legacymodels.inference.ai.azure.comendpoint with plain model names (gpt-4o).github-modelsuses the currentmodels.github.ai/inferenceendpoint documented in GitHub's Quickstart, which requires publisher-prefixed model names (openai/gpt-4o-mini,openai/gpt-5-mini, etc.).If
github-azure-modelsever stops responding, switch toprovider: github-modelswith a publisher-prefixedmodel.
api_type |
Path appended | Compatible with |
|---|---|---|
chat |
/chat/completions |
GitHub Models, Copilot, OpenAI, OpenRouter, Ollama, xAI, Google, Azure |
responses |
/responses |
OpenAI Responses API |
messages |
/messages |
Anthropic Claude |
text |
/completions |
Legacy completions endpoint |
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} api_key: ${{ secrets.GITHUB_TOKEN }}
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: github-models api_key: ${{ secrets.GITHUB_TOKEN }} model: openai/gpt-4o-mini
Requires a personal PAT with Copilot subscription stored as
COPILOT_PATsecret.
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: copilot api_key: ${{ secrets.COPILOT_PAT }} model: gpt-4o
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: openai api_key: ${{ secrets.OPENAI_API_KEY }} model: gpt-4o
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: anthropic api_key: ${{ secrets.ANTHROPIC_API_KEY }} model: claude-3-5-sonnet-20241022 max_tokens: "8192"
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: google api_key: ${{ secrets.GOOGLE_API_KEY }} model: gemini-2.0-flash
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: xai api_key: ${{ secrets.XAI_API_KEY }} model: grok-3
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: qwencloud api_key: ${{ secrets.QWEN_API_KEY }} model: qwen-plus
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: qwencloud-plan api_key: ${{ secrets.QWEN_API_KEY }} model: qwen-plus api_type: messages
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: openrouter api_key: ${{ secrets.OPENROUTER_API_KEY }} model: meta-llama/llama-3.3-70b-instruct
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: ollama api_key: "" api_url: http://my-server:11434/v1/chat/completions model: llama3
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: azure api_key: ${{ secrets.AZURE_OPENAI_API_KEY }} api_url: https://my-resource.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-01 model: gpt-4o
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: openai api_key: ${{ secrets.OPENAI_API_KEY }} api_type: responses model: gpt-4o
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} provider: custom api_key: ${{ secrets.MY_API_KEY }} api_url: https://my-llm-proxy.internal/v1/chat/completions model: my-model
- uses: n-devs/ai-code-review@v3 with: gh_pat: ${{ secrets.GITHUB_TOKEN }} repo: ${{ github.repository }} pr_number: ${{ github.event.pull_request.number }} api_key: ${{ secrets.GITHUB_TOKEN }} language: Thai
npm install npm run build # outputs dist/index.js via @vercel/ncc npm test # runs unit tests via vitest
Releases are deployed manually via Actions → Deploy Release → Run workflow with a version input (e.g. v2.1.0). Pick the branch to release from in GitHub's Use workflow from dropdown at the top of the Run workflow form — the branch list is filled in automatically. The release notes file is read from the selected branch and the release tag points at its head commit.
The workflow only deploys when the specified version matches a release notes file at docs/release/v<version>.md (see TEMPLATE.md) on the selected branch. If that file does not exist (or is empty, or the tag was already released), validation fails and nothing is deployed.
What the deploy does:
- Validates the version format and the existence of
docs/release/v<version>.md - Rebuilds
dist/and fails if the committeddist/is out of date withsrc/ - Creates the
v<version>tag and a GitHub Release whose body is the release notes file - Moves the major version tag (e.g.
v2) to this release, son-devs/ai-code-review@v3users get the update