{"slug": "a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works", "title": "A Free-Tier AI PR Reviewer: A GitHub Actions Workflow That Actually Works", "summary": "A developer built a GitHub Actions workflow that uses the open source MonkeyCode project's free model allowance to automatically review pull requests. The workflow captures a unified diff, sends it to the model with a prompt for structured JSON feedback, and posts deduplicated comments via the GitHub API. The design emphasizes working within free-tier limits by truncating diffs and handling duplicate comments.", "body_md": "Automated code review is one of the few AI workflows that pays for itself on the first pull request, and you can run it entirely on a free model allowance if you design the pipeline around the model's limits rather than against them. I built a GitHub Actions workflow that calls the open source MonkeyCode project's free model access to comment on pull requests, and the result is a reviewer that catches real issues without spamming the conversation. Disclosure: This article was prepared as part of MonkeyCode's product outreach.\n\nThe open source MonkeyCode project offers a free model allowance of ten million tokens and a free server option at the time of writing, and while the server is not required for this pipeline, it becomes useful if you later want to move the same logic to a hosted webhook. The setup is deliberately simple because the value is in the workflow, not in the model. The workflow triggers on pull_request events, checks out the repository with full history, and then passes a unified diff to a Python script that asks the model for structured feedback.\n\nThe first step is a workflow file that captures the diff between the base branch and the head branch. Using `fetch-depth: 0`\n\nensures that git can compute the exact changes, and the diff is saved to a temporary file that the review script can read. The environment variables carry the API key and the GitHub token, so no secrets appear in the repository.\n\n```\nname: AI PR Review\non:\n  pull_request:\n    types: [opened, synchronize]\njobs:\n  review:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Get diff\n        run: git diff origin/${{ github.event.pull_request.base.ref }}...HEAD > /tmp/diff.txt\n      - name: Run AI review\n        env:\n          MONKEYCODE_API_KEY: ${{ secrets.MONKEYCODE_API_KEY }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: python review.py /tmp/diff.txt\n```\n\nThe `pull_request`\n\nevent fires on both opened and synchronize, which means every new push to the branch will trigger a fresh review. That is usually desirable, but it also creates a duplicate-comment problem that the script must handle. The diff command compares the merge base with the head, so it only includes changes that are actually part of the pull request.\n\nThe Python script is where the design decisions matter. The diff is truncated to a reasonable size before being sent to the model, because a pull request can easily exceed the input limits of a free-tier endpoint, and the prompt asks for a JSON array of comments with a path, line, and body. The model is instructed to return an empty array when there are no issues, which makes the parsing logic trivial.\n\n``` python\nimport json, os, sys, urllib.request\n\ndef review_diff(diff_path):\n    diff = open(diff_path).read()[:12000]\n    prompt = (\n        \"You are a senior code reviewer. \"\n        \"Review this diff and return a JSON array of comments. \"\n        \"Each comment must have 'path', 'line', and 'body'. \"\n        \"Only comment on real issues. If no issues, return [].\\n\\n\"\n        f\"Diff:\\n{diff}\"\n    )\n    payload = json.dumps({\n        \"model\": \"free-model\",\n        \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n        \"temperature\": 0.2\n    }).encode()\n    req = urllib.request.Request(\n        \"https://api.monkeycode.example/v1/chat/completions\",\n        data=payload,\n        headers={\n            \"Content-Type\": \"application/json\",\n            \"Authorization\": f\"Bearer {os.environ['MONKEYCODE_API_KEY']}\"\n        }\n    )\n    with urllib.request.urlopen(req) as resp:\n        data = json.loads(resp.read())\n    content = data[\"choices\"][0][\"message\"][\"content\"]\n    try:\n        return json.loads(content)\n    except json.JSONDecodeError:\n        return []\n```\n\nThe script then posts each comment through the GitHub API, but only if the comment does not already exist. This deduplication step is essential because a workflow that triggers on both opened and synchronize events will otherwise repeat the same feedback on every push. The script also limits the total number of comments to five, which keeps the noise low and forces the model to prioritize the most important findings.\n\n``` python\ndef post_comments(comments):\n    existing = fetch_existing_comments()\n    posted = 0\n    for c in comments:\n        if posted >= 5:\n            break\n        key = (c[\"path\"], c[\"line\"], c[\"body\"])\n        if key in existing:\n            continue\n        create_comment(c[\"path\"], c[\"line\"], c[\"body\"])\n        posted += 1\n```\n\nThe workflow has real limitations that you should know before copying it. The free model will occasionally produce false positives, so the comments are suggestions rather than blockers, and the JSON output can be malformed, in which case the script simply skips that review cycle. The diff truncation means very large pull requests are only partially reviewed, and the model has no awareness of the surrounding codebase beyond the diff itself, so it cannot catch cross-file issues. This is not a replacement for a human reviewer; it is a triage layer that surfaces obvious problems before a human looks at the code.\n\nWhat surprised me most was how little code was needed to make this useful. The entire workflow is under a hundred lines, and the hardest part was not the API call but the deduplication and the output parsing, which are the same problems you would face with any external review tool. The free model allowance from MonkeyCode is generous enough for a small team's pull request volume, and the free server option becomes relevant if you want to move from GitHub Actions to a self-hosted webhook that can also handle other AI tasks.\n\nIf you want to try this pattern, check the MonkeyCode repository for the current free model access and free server details, because quotas and endpoints change over time. The workflow itself is the part worth keeping, and it transfers to any model provider you might use later. A free-tier AI reviewer is not a gimmick; it is a practical way to spend a small portion of your allowance on something that saves real attention every day.", "url": "https://wpnews.pro/news/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works", "canonical_source": "https://dev.to/byteio_3726/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works-hgk", "published_at": "2026-08-25 05:31:15+00:00", "updated_at": "2026-08-25 05:43:23.814866+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models"], "entities": ["GitHub Actions", "MonkeyCode", "Python"], "alternates": {"html": "https://wpnews.pro/news/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works", "markdown": "https://wpnews.pro/news/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works.md", "text": "https://wpnews.pro/news/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works.txt", "jsonld": "https://wpnews.pro/news/a-free-tier-ai-pr-reviewer-a-github-actions-workflow-that-actually-works.jsonld"}}