DEV Community

Emery Lin
Emery Lin

Posted on

Use MonkeyCode's Free Tokens to Build a PR Summarizer in CI That Doesn't Spam

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

The best AI code review bot is the one you don't notice. It comments once, updates itself, and never repeats the same advice on the same diff. That's not a model problem; it's a pipeline problem. Model quality matters, but a bot that double-posts or flakes will cost you more trust than it earns.

I've been testing a different pattern: a small GitHub Actions job that pulls the diff, asks a free model for a summary, and upserts a single PR comment. MonkeyCode's open-source project fits this because its free tier includes 10 million tokens and a free server option. As of September 2026, those numbers come from the project's public repo, but always check the README before building a workflow around a promo allowance.

Why AI review bots spam PRs

Most duplicates come from design, not from the model. Each push triggers a new workflow run, each run calls the model again, and each call writes a fresh comment. The model may also produce slightly different wording for the same diff, so simple text comparison won't catch the repeat.

The fix is to make the workflow stateful in three places:

  1. Cache the model response by diff hash.
  2. Comment with a marker so you can find and update it.
  3. Set a timeout so a slow free server can't block the pipeline indefinitely.

That last point is the one most tutorials skip. Free servers get busy. Your CI job should fail fast, not hang.

Workflow: one comment per PR

The artifact below is a complete GitHub Actions workflow with a Python script. It uses an OpenAI-compatible endpoint; MonkeyCode's docs will tell you the exact model name and route. Replace the placeholder with the real value before running it.

# .github/workflows/pr-summary.yml
name: ai-pr-summary
on:
  pull_request:
    types: [opened, synchronize]
permissions:
  contents: read
  pull-requests: write
jobs:
  summarize:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Generate or update summary
        env:
          MC_API_KEY: ${{ secrets.MC_API_KEY }}
          MC_BASE_URL: ${{ secrets.MC_BASE_URL }}
          GH_TOKEN: ${{ github.token }}
        run: |
          python .github/scripts/pr_summarizer.py
Enter fullscreen mode Exit fullscreen mode

The script: diff in, one comment out

Save this as .github/scripts/pr_summarizer.py. It uses only the standard library, so you don't need a language runtime beyond Python.

#!/usr/bin/env python3
import json
import os
import subprocess
import urllib.request

repo = os.environ["GITHUB_REPOSITORY"]
event = json.load(open(os.environ["GITHUB_EVENT_PATH"]))
pr = event["pull_request"]["number"]
base = event["pull_request"]["base"]["ref"]

# 1. Get the diff against the base branch.
#    If your diff is huge, truncate it; see "Limitations" below.
diff = subprocess.run(
    ["git", "diff", f"origin/{base}...HEAD"],
    capture_output=True,
    text=True,
).stdout[:12000]

# 2. Call the model through MonkeyCode's free server.
#    Replace "monkeycode-free" with the model name from the official docs.
payload = json.dumps({
    "model": "monkeycode-free",
    "messages": [{
        "role": "user",
        "content": (
            "Summarize the behavior changes in this PR in under 100 words. "
            "Use bullet points. Do not invent details.\n\n"
            f"```
{% endraw %}
diff\n{diff}\n
{% raw %}
```"
        ),
    }],
    "temperature": 0.2,
}).encode()

req = urllib.request.Request(
    os.environ["MC_BASE_URL"] + "/v1/chat/completions",
    data=payload,
    headers={
        "Authorization": "Bearer " + os.environ["MC_API_KEY"],
        "Content-Type": "application/json",
    },
)
with urllib.request.urlopen(req, timeout=60) as response:
    data = json.load(response)
summary = data["choices"][0]["message"]["content"].strip()

# 3. Upsert one comment per PR.
marker = "<!-- ai-pr-summary -->"
headers = {
    "Authorization": "token " + os.environ["GH_TOKEN"],
    "Accept": "application/vnd.github+json",
}
url = f"https://api.github.com/repos/{repo}/issues/{pr}/comments"

req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
    comments = json.load(response)

comment_body = marker + "\n\n" + summary
new_headers = {**headers, "Content-Type": "application/json"}

for comment in comments:
    if marker in comment["body"]:
        req = urllib.request.Request(
            f"{url}/{comment['id']}",
            data=comment_body.encode(),
            headers=new_headers,
            method="PATCH",
        )
        urllib.request.urlopen(req)
        break
else:
    req = urllib.request.Request(
        url,
        data=comment_body.encode(),
        headers=new_headers,
        method="POST",
    )
    urllib.request.urlopen(req)

print(summary)
Enter fullscreen mode Exit fullscreen mode

Add the two secrets in your repo settings: MC_API_KEY and MC_BASE_URL. If you're using MonkeyCode's free server, the base URL comes from the project's setup instructions.

How this stays quiet

The marker is the key. New comments are POSTed only when no marker exists. Every later push PATCHes the same comment instead of appending another one. That alone kills 90% of the spam.

For extra determinism, keep temperature low and pin the model version. A high temperature turns a summary bot into a lottery. You don't want the same diff to produce two different reviews on two identical runs.

Limitations and who should skip this

This is not a code review gate. It cannot reason about your private threat model, and it should never be the only thing between a PR and main. The free server is shared; expect occasional latency or a 503. The 60-second timeout means the job may fail loudly instead of blocking a merge silently.

You should also watch the token budget. Ten million tokens sounds like a lot until you start sending 12,000 characters of diff on every push. Cache by diff hash if you're on a busy monorepo. And if your team does hundreds of PRs a day, a shared free tier will not stay responsive — at that point you need your own MonkeyCode server.

One step at a time

Start with a single repository and one workflow. Let it run for a week, read the comments, and tune the prompt. Once the output is boring and consistent, add another repo.

If you're tired of bots repeating themselves, MonkeyCode's free token allowance is a low-risk sandbox for this pattern. Build the pipeline first, judge the model second.

Top comments (0)