Friday, 7:41 PM. My GitHub notification bell showed 23 unread items. Three PRs were waiting on me. One was a week-old draft. Another needed a design decision. The third was a one-line typo fix. The bell treated all three the same. That was the problem.
So I built a small bot. It reads open PRs, asks an AI to rank them, and prints a digest. It runs every thirty minutes on a free server. Total cost: one weekend and zero dollars. Total code: about forty lines.
Here is the build log. You get the working demo, the scope cut, and the list of skipped features.
The scope cut, before any code
I wrote a wishlist first. Then I deleted most of it. The rule was simple: the demo must survive one full day with no babysitting.
| Feature | Time cost | Value | Verdict |
|---|---|---|---|
| Rank PRs by urgency | 1 hour | High | Keep |
| Explain each rank | 20 minutes | High | Keep |
| Slack notification | 2 hours | Medium | Cut |
| Auto-comment on PRs | 3 hours | Medium | Cut |
| Web dashboard | 4+ hours | Low | Cut |
| Database persistence | 2 hours | Low | Cut |
The bot writes a Markdown file. That is the whole UI. A cron job triggers it. That is the whole scheduler. Everything else got cut. This is not laziness. It is how you ship a demo in one weekend.
Step 1: Fetch open PRs with plain Python
No framework. No SDK. Just the standard library and the GitHub REST API.
import json
import os
import urllib.request
REPO = os.getenv("GITHUB_REPO", "octocat/Hello-World")
TOKEN = os.getenv("GITHUB_TOKEN", "")
def fetch_open_prs():
url = f"https://api.github.com/repos/{REPO}/pulls?state=open"
req = urllib.request.Request(url)
if TOKEN:
req.add_header("Authorization", f"token {TOKEN}")
with urllib.request.urlopen(req) as res:
return json.load(res)
Public repos work without a token. Private repos need GITHUB_TOKEN. That is the whole data layer.
Step 2: Ask a free model to rank them
A list is not an order. I wanted to know which PR deserves attention first.
For this step I used MonkeyCode's free model access. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free tier currently includes a large token allowance (10M at the time of writing — verify the current number) and a free server option. Both matter here. The model call costs nothing, and the server keeps the cron job alive for $0.
Here is the ranking function. It is a plain chat-completions request. I ask for JSON back. No agent framework, no tools, one prompt.
AI_URL = os.getenv("MONKEYCODE_API_URL", "")
AI_KEY = os.getenv("MONKEYCODE_API_KEY", "")
def ask_ai(prs):
summary = "\n".join(
f"- #{p['number']}: {p['title']} by {p['user']['login']}"
for p in prs[:10]
)
prompt = (
"Rank the open PRs below by review urgency. "
"Return JSON only, as a list: "
'[{"number": <int>, "priority": <1-5>, "reason": "<why>"}].\n'
+ summary
)
payload = json.dumps({
"model": "<set-from-docs>",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.2,
}).encode()
req = urllib.request.Request(
AI_URL, data=payload,
headers={
"Authorization": f"Bearer {AI_KEY}",
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(req) as res:
return res.read().decode()
Three notes. Set MONKEYCODE_API_URL and the model name from the current docs; I refuse to hardcode an endpoint because endpoints change. Keep the batch at ten PRs; longer prompts burn tokens. And use low temperature; you want stable ordering, not creative ranking.
Step 3: Write the digest and fail loudly
The output is a Markdown file. Each run overwrites the last one. If the env vars are missing, the bot stops with an error. Quiet failures rot. Loud ones get fixed.
def main():
if not AI_URL or not AI_KEY:
raise SystemExit("Set MONKEYCODE_API_URL and MONKEYCODE_API_KEY first.")
prs = fetch_open_prs()
if not prs:
print("No open PRs. Done.")
return
result = ask_ai(prs)
with open("digest.md", "w") as f:
f.write("## PR Triage Digest\n\n")
f.write(result)
print(f"Digest written: {len(result)} bytes")
if __name__ == "__main__":
main()
The digest is a file. You can read it, commit it, or email it. A file is enough for one developer.
Step 4: Deploy on a free server
Now the second free tier feature comes in: the server. No VPS bill, no Docker, no Kubernetes. Just a box, a clone, and cron.
ssh user@your-free-server
git clone https://github.com/you/pr-triage.git
cd pr-triage
cp .env.example .env
The .env.example is boring on purpose.
GITHUB_REPO=you/repo
GITHUB_TOKEN=
MONKEYCODE_API_URL=
MONKEYCODE_API_KEY=
Then the schedule. Thirty minutes is the sweet spot. Every minute is noisy. Every six hours is too slow.
crontab -e
*/30 * * * * cd /home/you/pr-triage && python3 triage.py >> digest.log 2>&1
Test by hand first. Run python3 triage.py once. If the model returns malformed JSON, fix the prompt before you trust the cron. A bad cron job is a silent liar.
What I skipped (and why)
- Auto-commenting on PRs. Write access means more secrets and more risk. Read-only is safer.
- Slack alerts. Notifications add auth, retries, and rate limits. The file is fine.
- A web dashboard. The digest is one page. A dashboard is a second project.
- A database. The last digest is the only state. No history, no migrations.
- Retry logic. A failed run shows in the log. A retry loop hides it.
Limitations: who should not use this
This is a demo, not a product. Do not turn it into a team tool without auth and persistence. Do not treat the ranking as truth. The model sees titles, not your deadlines, not your discussions, not your CI status. A "priority 1" is a suggestion, not a directive.
The bot also misses diff-level context. Diffs would improve the ranks, but they cost many more tokens. A title-based rank catches the obvious stalls. If you want diff awareness, fetch the diff only after the bot flags a PR as high priority. That is a clean next weekend.
And about the free tier itself: the 10M token allowance and the free server are current as of this writing, but quotas change. Check MonkeyCode's docs before you rely on any number. That is not skepticism. That is how every free tier works.
The result
Six focused hours on a weekend. Zero dollars spent. A working artifact: forty lines of Python, one cron entry, and a digest that tells me which PR to open first. The dashboard, the comments, and the notifications all stayed on the cutting room floor. I do not miss them.
If you want to test the free tier, copy the script, point it at your own repo, and let it run for a day. Then delete the features you do not miss. That is the whole point of a weekend demo.
Top comments (0)