Reads your diff like a senior engineer. Never leaves your network.
AI Code Reviewer is a GitHub Action that reviews every pull request with a self-hosted Ollama model, with full file context, inline comments on the exact line, and no cloud API bill.
A GitHub Action that reviews pull requests like a senior engineer: bugs, security issues, simplification opportunities, and style, using a self-hosted Ollama model instead of a paid external API.
git clone https://github.com/PotatoNoodIes/code_reviewer.git
Four categories, every file, every push.
The same system prompt runs on every changed file: no generic linting rules, no invented nits. If a file is clean, it says so.
Logic & correctness
Off-by-ones, wrong conditionals, mishandled errors: things that will misbehave, crash, or return the wrong answer.
Vulnerabilities
Injection, hardcoded secrets, unsafe deserialization, missing auth or validation.
Reuse & clarity
Code that works but could be clearer, shorter, or shouldn't be duplicated.
Naming & idiom
Formatting and naming issues a careful teammate would flag in passing.
One pull request, six steps.
No polling, no local checkout. Every step reads from the GitHub API directly, driven by the event that triggered the run.
Read the trigger
A pull_request event (opened or synchronize) fires the action; it parses the payload for owner, repo, PR number, and head SHA.
Select files
Changed files are listed, filtered against ignore-patterns, and capped at max-files, largest diffs reviewed first and the rest reported as skipped.
Parse the diff
Each unified-diff patch is parsed to find exactly which line numbers a comment is allowed to land on.
Fetch full context
The complete file comes from the Git Blob API at the head commit, so the model reads whole functions, not fragments.
Ask the model
File, diff, and a fixed senior-review system prompt go to Ollama, constrained to a JSON schema. Malformed output is retried, then skipped, never allowed to break the run.
Validate & post
Every finding is re-checked against the file's real commentable lines before it's trusted. Comments post one GitHub review per file, hashed to skip duplicates on the next push.
Point it at a self-hosted runner.
Ollama needs to be reachable from the machine running the job, realistic for a self-hosted runner, not for GitHub-hosted ones.
on: pull_request: types: [opened, synchronize] permissions: pull-requests: write # required, default GITHUB_TOKEN is read-only jobs: review: runs-on: self-hosted steps: - uses: PotatoNoodIes/code_reviewer@v1 with: github-token: ${{ secrets.GITHUB_TOKEN }} ollama-url: http://localhost:11434 ollama-model: qwen2.5-coder:7b
| Input | Default | Description |
|---|---|---|
| github-token | required | Reads PR data and posts comments. |
| ollama-url | localhost:11434 | Base URL of the Ollama server, see note below. |
| ollama-model | qwen2.5-coder:7b | A code-focused model reviews far better than a general chat model. |
| ollama-format-mode | schema | JSON-schema-constrained decoding; falls back to plain json on older Ollama. |
| max-files | 15 | Cap per PR, largest diffs reviewed first. |
| ignore-patterns | vendor, lockfiles… | Comma-separated globs of paths to skip. |
| review-event | COMMENT | COMMENT, APPROVE, or REQUEST_CHANGES. |
| dry-run | false | Print findings to the log instead of posting. |
This ships as a Docker container action, which runs in its own network namespace: localhost inside the container is not the runner host, even on self-hosted infrastructure.
- Bind Ollama to all interfaces (
OLLAMA_HOST=0.0.0.0) and pointollama-urlat the runner's real LAN IP or Docker bridge gateway. - Or run the self-hosted runner itself with
--network host, so job containers inherit host networking andlocalhostresolves as expected.
What actually shows up in the PR.
The same sample.go, the same four findings, rendered as the review GitHub would show. Flip review-event below to see how each mode behaves.
const apiKey = "sk-live-abc123secretkey"func getUser(db *sql.DB, name string) { query := "SELECT * FROM users WHERE name = '" + name + "'" rows, _ := db.Query(query)SQL injection via string concatenation. Use a parameterized query.
total := 0 for i := 0; i <= len(nums); i++ { total += nums[i]Off-by-one: i <= len(nums) reads one index past the end of the slice.
func printAll(items []string) { for i := 0; i < len(items); i++ { fmt.Println(items[i])This loop duplicates the one below it; one pass is enough.
Left 4 comments across 1 file. A COMMENT review never blocks or requires action before merging.
What v1 doesn't do yet.
- No stale-comment resolution: the REST API has no thread-resolve endpoint (needs GraphQL's
resolveReviewThread), so comments for since-fixed issues stay open. max-file-linesandollama-concurrencyare accepted but not yet enforced; files review sequentially, uncapped by line count.- Renames with no content change, and diffs GitHub declines to compute, are skipped; there's no diff to validate a finding against.
- Schema-constrained decoding needs a reasonably recent Ollama; older servers should use
ollama-format-mode: json.
No PR required to try it.
Unit tests need no network. The dry run hits a real PR but never posts a comment.
$ make build test vet ok internal/githubapi 0.006s ok internal/ollama 0.002s ok internal/review 0.006s $ GITHUB_TOKEN=<token> DRY_RUN=true \ go run ./cmd/reviewer --owner <owner> --repo <repo> --pr <number>
Go ahead, type something.
A live 1977-style terminal. Click in and type HELP to see what it knows.
An interactive retro computer terminal. Focus the terminal and type commands such as HELP, LIST, or REVIEW SAMPLE.GO to run a scripted review and see its output.
HELP
Hardcoded secret in source code. Move to environment variables or a secrets manager.