Self-hosted AI code review for GitLab and GitHub. Run on your own infrastructure. Bring your own model. Use your own agent subscription to pay per review, not per developer.
ReviewPhin listens for GitLab and GitHub code review events, runs a multi-agent AI review, and publishes findings through each provider's native review model while only modifying content it created itself.
All model calls go through a configured harness (currently Copilot CLI, but more may come) so you can plug in either a subscription model or an OpenAI-compatible API.
Internally, code-review operations flow through a platform adapter layer with built-in GitLab and GitHub implementations.
The container image serves the documentation hub at /docs/. During local Docker runs, open http://localhost:3000/docs/. The docs source lives in docs/; the homepage is built only for static-site hosts when explicitly enabled.
Created by @rgembalik with support from CodeWave
- Official docs
- Quickstart with Docker
- Kubernetes / Helm
- Adding tenants
- Using ReviewPhin
- How it works
- Review pipeline
- Technologies
- Environment variables
- Inspiration & motivation
- CLI reference
- Model providers
- Code review platform providers
- Storage providers
The published image is cdwv/reviewphin. It bundles the GitHub Copilot CLI for default-mode operation and exposes the reviewphin CLI entrypoint.
Copy the example environment file and fill in your settings:
cp .env.example .env.dockerAt minimum, set one GitHub token (for Copilot CLI mode):
GH_TOKEN=<your-github-token>If you prefer to configure separate GitHub API tokens for different projects, you can skip this environment variable and configure model profiles.
See Environment variables and Model providers for full options.
docker compose up -dThe compose file mounts ./data to /app/data (SQLite database + run logs) and ./tmp to /app/tmp (hydrated workspaces). Both directories are created automatically.
ReviewPhin receives GitLab webhooks over HTTPS. If your GitLab instance cannot reach your host directly, create a temporary tunnel:
# Using cloudflared (no account needed for one-off tunnels)
cloudflared tunnel --url http://localhost:3000
# Or using ngrok
ngrok http 3000Note the public HTTPS URL; you will use it in the webhook settings.
For production, place ReviewPhin behind a TLS-terminating reverse proxy or use the Helm chart.
curl http://localhost:3000/healthz
# {"status":"ok"}A Helm chart is published to GHCR as oci://ghcr.io/cdwv/charts/reviewphin. Local checkouts can also install the chart from .chart/. It deploys one Deployment, one Service on port 3000, and one PersistentVolumeClaim for /app/data and /app/tmp.
REVIEWPHIN_VERSION=0.13.3
REVIEWPHIN_CHART=oci://ghcr.io/cdwv/charts/reviewphin
kubectl create namespace reviewphin
kubectl create secret generic reviewphin-env \
--namespace reviewphin \
--from-env-file=.env.production
helm upgrade --install reviewphin "${REVIEWPHIN_CHART}" \
--namespace reviewphin --create-namespace \
--version "${REVIEWPHIN_VERSION}" \
--set application.envSecret=reviewphin-env \
--set persistence.size=1GiThe chart defaults to cdwv/reviewphin with a tag matching the chart appVersion. It requires application.envSecret; put PUBLIC_URL, model authentication such as GH_TOKEN or COPILOT_GITHUB_TOKEN, and any storage settings in .env.production before creating the secret. If you prefer to configure separate GitHub API tokens for different projects, omit the token from this secret and configure model profiles.
Ingress and Gateway API HTTPRoute resources are available as opt-in chart features and are disabled by default.
helm upgrade --install reviewphin "${REVIEWPHIN_CHART}" \
--namespace reviewphin --create-namespace \
--version "${REVIEWPHIN_VERSION}" \
--set application.envSecret=reviewphin-env \
--set persistence.size=1Gi \
--set ingress.enabled=true \
--set ingress.hosts[0].host=reviewphin.example.comhelm upgrade --install reviewphin "${REVIEWPHIN_CHART}" \
--namespace reviewphin --create-namespace \
--version "${REVIEWPHIN_VERSION}" \
--set application.envSecret=reviewphin-env \
--set persistence.size=1Gi \
--set httpRoute.enabled=true \
--set httpRoute.parentRefs[0].name=main-gateway \
--set httpRoute.hostnames[0]=reviewphin.example.comA tenant is a single project on a connected code review platform. One ReviewPhin instance can serve multiple tenants, but each tenant is configured only for one project or repository.
Use one of:
- a project access token (scoped to one project)
- a group access token (scoped to a group)
- a personal access token belonging to a dedicated bot user (you must add it to project with at least Developer role)
Required scope: api - the only scope ReviewPhin uses on the token. It is needed to read code review data, create/update/resolve bot-owned discussions, and to clone the repository over Git-over-HTTPS during workspace hydration. ReviewPhin never touches content it did not author itself; the api scope is used solely for the read/write actions described in Review pipeline.
Required project membership: Developer or higher (needed to resolve code review discussions and read repository contents).
Use the GitLab UI or API to collect the project's id.
The examples below use the image-provided reviewphin CLI for readability. With Docker Compose, run them as docker compose run --rm worker reviewphin ...; from a local checkout, replace reviewphin with pnpm cli. See the CLI reference for full invocation details.
reviewphin platform connection add \
--name main-gitlab \
--platform gitlab \
--base-url https://gitlab.example.com \
--api-token <your-gitlab-token>
reviewphin tenant add \
--connection main-gitlab \
--project-id 123 \
--webhook-secret <your-webhook-secret>--platform currently defaults to gitlab, so the flag is optional. To assign a specific model profile at registration time, add --model-profile <name>. See Model providers for profile setup.
For GitHub, register a GitHub App connection first. The command prints an expiring setup URL for the App Manifest flow:
reviewphin platform connection add \
--platform github \
--name main-github \
--owner example-orgSet PUBLIC_URL in the worker environment before running this command.
Open the printed setup URL, complete registration and installation on GitHub, then add a repository tenant:
reviewphin tenant add \
--platform github \
--connection main-github \
--repository example-org/example-repositorySee GitHub platform provider for the full GitHub connection lifecycle.
During local development, pnpm dev logs a
http://localhost:<PORT>/github/setup/samples URL for previewing the GitHub
setup screens without creating a setup token or starting the GitHub App flow.
The sample pages use example data and are served outside /setup/github, so
they do not touch storage, call GitHub, or complete any setup step. These
sample routes are not mounted by the production server.
For GitLab, add the webhook manually.
In the project's Settings → Webhooks:
| Field | Value |
|---|---|
| URL | https://your-host/webhooks/gitlab |
| Secret token | the same value you passed as --webhook-secret |
| Trigger | Note events only |
/webhooks/gitlab/note is still accepted as a backward-compatible legacy alias, but /webhooks/gitlab is the canonical route for new setups.
Save, then use the Test button (select Note events) to verify ReviewPhin receives the delivery and returns 202 Accepted.
For GitHub, the App Manifest setup configures the webhook URL as
https://your-host/webhooks/github. The generated App must stay installed on
the repositories you register as tenants.
reviewphin tenant listThe JSON output includes each tenant's id, key, and platform. See CLI reference for all tenant and model-profile commands.
Post a merge request comment that mentions the bot:
@reviewphin review this
ReviewPhin queues a job, hydrates the code review, and creates or updates bot-owned discussions for each finding plus a summary comment at the top of the discussion list.
On first run this is a full review covering all changed files. On subsequent runs for the same code review it is an incremental re-review focused on files changed since the last run.
GitHub uses a Check Run action as the primary manual trigger. Open the pull request's ReviewPhin Check Run and click Run Review. ReviewPhin provisions the Check Run when a pull request is opened or reopened, when its head changes, and when a GitHub tenant is added for repositories that already have open pull requests.
ReviewPhin also accepts /reviewphin review, @reviewphin review, and
mentions of the generated App slug in pull request comments as compatibility
triggers, but GitHub does not expose those as native slash commands or reliable
mention autocomplete entries.
To ignore the previous review and rescan everything from scratch:
@reviewphin full review
Other accepted phrasings: full rescan, fresh full review, rescan everything.
Replies inside a bot-owned review discussion automatically queue a new pass scoped to that discussion. In GitLab this is a discussion note. In GitHub this is a reply inside a ReviewPhin-owned inline review thread. You do not need to mention the bot again:
# Inside a bot discussion:
Can you suggest a more readable variable name here?
To store a durable note in the project memory (written to the selected memory backend):
@reviewphin for future reference, we always prefer functional React components over class components
Other triggers: remember, going forward, team policy, always prefer, please prefer.
Project memory is owned by the configured code-review platform and storage provider. GitHub uses the configured ReviewPhin storage provider. GitLab uses the project wiki when GitLab project metadata reports the wiki feature enabled; otherwise it uses the configured storage provider. If a GitLab project later enables wiki after store-backed memory exists, the wiki wins and the store row is deleted.
Add a directive in the code review description (the merge request description in GitLab, not a comment):
/reviewphin-profile byok-gpt5.4
This selects a named model profile for every review run on that code review. To read more about named model profiles, read Model profiles.
- A developer triggers a review from GitLab comments, GitHub pull request comments, or the GitHub Run Review Check Run action.
- ReviewPhin receives the platform webhook, validates the signature, resolves the tenant, and queues a job.
- The Router hydrates the code review: it checks out the exact commit, fetches diffs, notes, and any project instruction files.
- The Reviewer (a two-agent pipeline) analyses the changes and produces structured findings with severity, category, optional line anchors, and inline code suggestions.
- The Chatter handles follow-up replies, conversational questions, and durable project memory updates.
- Findings are reconciled back through the platform provider as bot-owned review output. Obsolete threads are resolved where the platform supports it; the summary comment is updated.
All code and data stay on your infrastructure. The worker calls the configured model API and the connected code review platform API; nothing else leaves the network.
ReviewPhin uses three logical components for each triggered review:
The webhook handler validates the platform signature, classifies the trigger (direct mention, Check Run action, follow-up reply, or summary note reply), deduplicates concurrent jobs, and enqueues a review task. No model calls happen here.
The main agent runs as two sequential subagents inside a single model session:
- context-analyst - explores the hydrated workspace using
glob,ripgrep, and file-read tools to gather the context most relevant to the changed files. - review-author - produces structured findings: severity, category, body text, optional diff anchor, and optional inline code suggestion.
The reviewer selects one of three modes based on trigger context:
- first-pass-full - first review of the MR, or an explicit full rescan
- incremental-rereview - focused on files changed since the last review
- follow-up-discussion - scoped to one existing discussion
A lightweight agent that runs after the reviewer (when applicable). It handles:
- conversational replies to questions or wording requests
- project memory decisions (
add_memory_entrytool writes to the selected memory backend) - reply text for explicit follow-up targets
Chatter uses the textGenerationModel from the active profile (falling back to the review model when unset), keeping lighter interactions cheaper.
| Layer | Technology |
|---|---|
| Runtime | Node.js 22+ source runtime, Node.js 26 Docker image, TypeScript 6 |
| HTTP server | Fastify 5 |
| AI runtime | @github/copilot-sdk (Copilot CLI wrapper) |
| Model APIs | native Copilot, vLLM, Azure OpenAI, Anthropic |
| Storage (default) | SQLite via Node.js node:sqlite |
| Storage (optional) | Flotiq headless CMS |
| Logging | pino (structured JSON) |
| Validation | zod |
| Packaging | Docker, multi-stage build |
| Orchestration | Helm (Kubernetes) |
All variables are optional unless noted. For local Docker from source, put them in .env.docker; for local runs, use .env.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
HOST |
0.0.0.0 |
Bind address |
PUBLIC_URL |
http://localhost:<PORT> |
Public app URL used to print initial provider setup links |
REVIEWPHIN_ALLOW_BOT_INDEXING |
false |
Allow crawler indexing for docs paths only (/docs/*) when set to true |
REVIEWPHIN_BOT_INDEXING_ALLOWED_HOSTS |
(none) | Comma-separated host allowlist for docs indexing; non-docs stay blocked always |
LOG_LEVEL |
info |
fatal | error | warn | info | debug | trace | silent |
STORAGE_PROVIDER_MODULE |
built-in SQLite | Module path or package name for a custom storage adapter |
PLATFORM_MODULES |
gitlab,github |
Comma-separated platform provider modules. Supports built-ins, paths, and packages |
SQLITE_DATABASE_PATH |
./data/review-worker.sqlite |
SQLite file path (ignored when a custom storage module is set) |
RUN_LOG_DIR |
./data/run-logs |
Root directory for per-review run artifacts |
WORKSPACE_ROOT |
./tmp/review-workspaces |
Scratch directory for hydrated repositories |
MAX_JOB_RETRIES |
3 |
Retry attempts for failed review jobs |
RETRY_BACKOFF_MS |
5000 |
Delay (ms) between retries |
COPILOT_TIMEOUT_MS |
180000 |
Model session timeout in milliseconds |
COPILOT_SDK_LOG_LEVEL |
(none) | SDK log verbosity: none | error | warning | info | debug | all |
COPILOT_CLI_PATH |
/usr/local/bin/copilot (image) |
Path to the Copilot CLI binary |
REVIEWPHIN_MEMORY_ENABLED |
true |
Enable per-project memory |
REVIEWPHIN_MAX_PROMPT_MEMORY_CHARS |
5000 |
Character budget for injected project memory |
GH_TOKEN / GITHUB_TOKEN / COPILOT_GITHUB_TOKEN |
(required for Copilot mode) | GitHub PAT with Copilot Requests permission |
For model profile setup (BYOK providers, Azure OpenAI, etc.) see Model providers. For custom storage adapters see Storage providers. For custom code review platform providers see Code review platform providers.
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Liveness probe, returns {"status":"ok"} |
POST |
/webhooks/<platform> |
Platform webhook receiver. Built-ins use /webhooks/gitlab and /webhooks/github |
* |
/setup/<platform> |
Optional platform setup handler when the provider exposes one |
GET |
/github/setup/samples |
Dev-server-only GitHub setup template previews with example data |
POST |
/webhooks/gitlab/note |
Deprecated GitLab compatibility alias |
ReviewPhin exists because of the work other people have already done in this space. It is not trying to compete with them - it is trying to fill a specific gap.
- CodeRabbit is the single biggest inspiration. The shape of the review pipeline, the way findings are reconciled as bot-owned discussions, and the overall feel of conversational follow-ups all started from "what CodeRabbit does, but self-hosted". If you can afford their per-developer pricing, they are almost certainly the better product.
- Greptile is another tool in a similar space that showed me how effective the conversational, discussion-driven review format can be. I looked into it much less deeply than CodeRabbit, but I'd be lying if I said I didn't read through their docs before starting my own project. Same caveat applies - a polished hosted product run by people who do this full time and probably with better results than what ReviewPhin can provide.
- GitHub Copilot code review showed me that copilot is capable of good reviews on some of my projects. The catch for me was that it lives inside GitHub, and the projects I work on primarily live on GitLab.
The motivation, then, is the intersection of three constraints those tools did not cover for me:
- Affordability for small teams. Per-developer pricing scales linearly with headcount even when only a fraction of MRs need AI review. A single Copilot seat (or a self-hosted model) can drive reviews for a whole team and usually costs less than one CodeRabbit/Greptile seat. If you have spare quotas on your Copilot license, you might even be able to run ReviewPhin at no model extra cost.
- Bring-your-own model, including private ones. I wanted to point the reviewer at models hosted inside our own infrastructure - e.g. a Qwen 3 27B running on internal GPUs - to push the cost down further and keep code inside the network. ReviewPhin's harness/profile system exists primarily to make that possible.
- GitLab first. ReviewPhin starts from self-hosted GitLab in mind. Why? Because I use it as daily driver for most of my development work. (see Code review platform providers).
So: everything is yours. Storage, model, subscription, hosting. You know what you pay for because it's all yours - and if any of the projects above fits your team and budget better, please use them. They are great.