DEV Community

Charlie Xu
Charlie Xu

Posted on

A PR Review Bot That Costs Zero: Free Models on a Free Server

Code review is the bottleneck that appears right after AI accelerates code production. A developer can generate a pull request in minutes, but a human reviewer still needs time to read it, reason about it, and catch the subtle mistakes. The asymmetry is getting worse. One trending DEV discussion asked what developers do while AI codes, and the honest answer for many is: review the code AI just wrote.

This article builds a small, practical answer to that bottleneck. It walks through a PR review bot that runs on free infrastructure, uses free model access, and produces a structured review report that a human can verify in seconds instead of minutes. The goal is not to replace the human reviewer. The goal is to shrink the time between "PR opened" and "human attention focused on the right lines."

Why a bot instead of another IDE plugin

IDE plugins are great while you are writing code. They fail when the code lands in a pull request, because the reviewer's context is different: they see a diff, not a session. A bot that watches the PR endpoint has three advantages:

  1. It reviews the exact diff that will be merged, not the state of the editor.
  2. It runs on a schedule or webhook, so the review exists before the human opens the PR.
  3. It produces an artifact — a markdown report — that can be archived, compared, and audited.

The architecture is simple: a webhook receives the PR event, a script extracts the diff, a model reviews it, and the result is posted back as a comment. The whole loop fits on a free server.

What the free tier actually covers

MonkeyCode is an open-source project, and its free offering currently includes two things this workflow needs: free model access and a free server option. The free model access means the review step does not require a paid API key. The free server means the webhook listener and the review script can run without a cloud bill.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Specific quotas, model names, and speed limits change over time, so this article does not promise exact numbers. What matters is the pattern: a free model plus a free server is enough to run a useful review bot for a small team or a personal project.

The review loop

The bot follows a five-step loop. Each step is small enough to debug on its own.

  1. Receive the PR webhook payload.
  2. Fetch the diff from the repository.
  3. Split the diff into logical chunks.
  4. Send each chunk to the free model with a review prompt.
  5. Aggregate the model's comments into a single markdown report.

The split step matters more than it looks. Sending a 500-line diff as one prompt produces shallow feedback. Splitting it into per-file or per-hunk chunks produces specific, line-anchored comments. The free model handles small, focused inputs better than one giant blob.

The server setup

The free server provides a Linux environment with a terminal. The setup takes about ten minutes:

mkdir -p ~/review-bot/{logs,reports}
cd ~/review-bot
python3 -m venv .venv
source .venv/bin/activate
pip install flask requests
Enter fullscreen mode Exit fullscreen mode

That is the entire dependency list. Flask handles the webhook, requests fetches the diff, and the model API is called over HTTP. No database, no queue, no container orchestration.

The webhook listener

A minimal Flask app listens for GitHub-style webhook events. The code below is intentionally small so it can be read in one pass:

# app.py
import json
import hmac
import hashlib
import os
from flask import Flask, request, jsonify
from reviewer import run_review

app = Flask(__name__)
SECRET = os.environ.get("WEBHOOK_SECRET", "change-me")

def verify_signature(payload_body, signature_header):
    if not signature_header:
        return False
    digest = hmac.new(SECRET.encode(), payload_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={digest}", signature_header)

@app.route("/webhook", methods=["POST"])
def webhook():
    body = request.get_data()
    if not verify_signature(body, request.headers.get("X-Hub-Signature-256")):
        return jsonify({"error": "bad signature"}), 401
    event = request.headers.get("X-GitHub-Event")
    if event != "pull_request":
        return jsonify({"status": "ignored"}), 200
    payload = json.loads(body)
    if payload.get("action") not in ("opened", "synchronize"):
        return jsonify({"status": "ignored"}), 200
    run_review.delay(payload)
    return jsonify({"status": "queued"}), 202

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
Enter fullscreen mode Exit fullscreen mode

The signature check is not optional. A public webhook endpoint without verification lets anyone trigger model calls, which burns quota and opens a spam hole. The run_review.delay call is a placeholder for a background worker; for a single-user bot, a thread or a simple queue is enough.

The reviewer module

The reviewer module does the real work. It fetches the diff, splits it, calls the free model, and writes a report.

# reviewer.py
import requests
import os
from pathlib import Path

MODEL_ENDPOINT = os.environ.get("MODEL_ENDPOINT", "https://api.monkeycode.ai/v1/chat/completions")
MODEL_NAME = os.environ.get("MODEL_NAME", "free-model")

REVIEW_PROMPT = """
You are a senior code reviewer. Review this diff chunk.
Focus on:
1. Correctness bugs (off-by-one, race conditions, null derefs)
2. Security issues (injection, unsafe deserialization, hardcoded secrets)
3. Readability problems that will confuse future maintainers

For each issue, reply with:
- File and line number
- Severity: critical / warning / nit
- One-sentence explanation
- Suggested fix

If the chunk is clean, reply with "No issues found."
"""

def fetch_diff(repo_url, pr_number, token):
    headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3.diff"}
    url = f"https://api.github.com/repos/{repo_url}/pulls/{pr_number}"
    r = requests.get(url, headers=headers)
    r.raise_for_status()
    return r.text

def split_diff(diff_text, max_lines=80):
    chunks = []
    current = []
    for line in diff_text.splitlines():
        current.append(line)
        if line.startswith("diff --git") and current:
            if len(current) > 1:
                chunks.append("\n".join(current[:-1]))
            current = [line]
        elif len(current) >= max_lines and line.startswith("@@"):
            chunks.append("\n".join(current))
            current = []
    if current:
        chunks.append("\n".join(current))
    return chunks

def review_chunk(chunk):
    payload = {
        "model": MODEL_NAME,
        "messages": [
            {"role": "system", "content": REVIEW_PROMPT},
            {"role": "user", "content": chunk},
        ],
        "temperature": 0.2,
    }
    r = requests.post(MODEL_ENDPOINT, json=payload, timeout=60)
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"]

def run_review(payload):
    repo = payload["pull_request"]["head"]["repo"]["full_name"]
    number = payload["number"]
    token = os.environ["GITHUB_TOKEN"]
    diff = fetch_diff(repo, number, token)
    chunks = split_diff(diff)
    findings = []
    for i, chunk in enumerate(chunks):
        result = review_chunk(chunk)
        findings.append(f"## Chunk {i+1}\n\n{result}")
    report = "\n\n".join(findings)
    Path(f"reports/pr-{number}.md").write_text(report)
    post_comment(repo, number, report, token)
Enter fullscreen mode Exit fullscreen mode

The MODEL_ENDPOINT and MODEL_NAME values are placeholders. The actual endpoint and model identifier come from the MonkeyCode free tier configuration at the time you set it up. Check the project documentation for the current values instead of assuming these strings work.

The decision table for using this approach

A bot like this is not universally useful. Here is a decision table to check before adopting it:

Situation Use the bot? Why
Solo developer, personal projects Yes Catches obvious mistakes before you review your own PR
Small team, no dedicated reviewer Yes Gives a first pass before the human spends attention
Team with strict style guidelines Partially The bot misses project-specific conventions; keep human review
Security-sensitive codebase No The model is a heuristic, not an auditor
High-volume OSS project No Comment spam will annoy maintainers; use a status check instead
Absolute beginners No They cannot distinguish model noise from real issues

The pattern that emerges: the bot is most useful when the human reviewer is competent but time-constrained. It is harmful when the human cannot judge the model's output.

Limitations and failure modes

The bot has real limitations, and pretending otherwise would be dishonest.

First, the free model is not a static product. The model name, endpoint, and quota can change without notice. The code above isolates those values in environment variables so a change is a config edit, not a rewrite. Still, a curriculum or a company process should not depend on a specific free tier remaining identical.

Second, the model can hallucinate line numbers. The diff chunk is a snapshot, and the model's line references may not match the final file. The report should say "approximate location" and the human should verify before commenting on the actual PR.

Third, the bot has no memory of the repository's history. It cannot know that a certain file is legacy code with a known bug, or that a naming convention exists for a reason. It reviews the diff in isolation, which is exactly why the human stays in the loop.

Fourth, the webhook approach requires a public endpoint. The free server provides one, but exposing a webhook means the server must be patched and the secret must be rotated. This is not a zero-maintenance system; it is a low-maintenance system.

Who should not use this

Teams that already have a mature review process will find the bot redundant. Teams that treat the model's output as authoritative will find the bot dangerous. The bot is a filter, not a judge. If the team does not have at least one person who can confidently say "the model is wrong here," the bot will amplify mistakes instead of catching them.

The workflow in practice

A typical session looks like this. A developer opens a PR. The webhook fires, the bot fetches the diff, splits it into chunks, and sends each chunk to the free model. Two minutes later, a comment appears on the PR with a structured report: three critical issues, five warnings, two nits. The human reviewer opens the report, reads the critical items first, verifies each one against the code, and either fixes or dismisses them. The warnings get a quick scan. The nits are ignored.

The total human time drops from reading a full diff to reading a prioritized list. That is the entire value proposition. It does not replace judgment; it preserves judgment for the lines that matter.

Getting started

To run this yourself, you need three things: a MonkeyCode free account for the model access, the free server for the webhook, and a GitHub token with read access to the repository. The code in this article is the complete skeleton. Add a queue if you expect concurrent PRs, add a database if you want to track review history, and add tests if you plan to maintain it beyond a weekend.

If you want to try the full loop, the free tier on MonkeyCode gives you both the model access and the server in one place. The setup is the code above, plus a webhook URL and a secret. The rest is a weekend of tweaking prompts and a willingness to read the model's output critically.

Top comments (0)