I Built a Self-Running GitHub Triage Agent (250 Lines, Free LLM)
Maintainers drown in issue queues. Every repo has a backlog of open issues
that nobody got around to classifying — bugs sitting next to feature
requests next to duplicates. So over a weekend I built an agent that does
the boring part: reads a repo's open issues + PRs, classifies each one,
assigns priority, and renders a dashboard. ~250 lines, no frameworks,
no build step, and the LLM part costs nothing.
The agent loop (3 steps)
-
Fetch. GitHub's public REST API lists open items for
owner/repo. -
Reason. Each item goes to an LLM with a strict JSON output contract:
{"category": "bug|feature|question|duplicate|docs", "short": "...", "priority": 1-5}. Because free-tier endpoints get overloaded (hello, HTTP 503), I rotate through ~5:freemodels until one answers. - Render. A single-file page (inline CSS/JS, no deps) shows color-coded cards sorted by priority, each linking back to the original issue.
What a real run looks like
On facebook/react (22 open items at scan time): 22/22 classified, zero
errors. Highlights:
- p1 bug — "Freezing props prevents later assignments causing …"
- p1 bug — "Unbounded debug info causes RangeError on large …"
- p1 question — "Performance optimization inquiry…"
On typeorm/typeorm: a clean mix of bug, feature, docs verdicts with
one-word takes like "Remove dayjs dependency to reduce TypeORM
dependencies".
Why this matters for agents
The interesting part isn't the code — it's the pattern. A triage agent is
a tiny vertical slice of what autonomous engineering looks like: read real
state, make structured decisions with an LLM, present actionable output.
Same skeleton powers PR review, dependency drift reports, or release-note
generation. Swap the data source, keep the loop.
The honest bits
- Free LLM tiers are flaky: 503s are the norm on popular models. Model rotation with fallback is non-negotiable.
- Small models sometimes return sloppy JSON. The parser takes the first
{...}block and tolerates markdown noise. - GitHub rate limits (60 req/hr unauthenticated) matter for big scans; a PAT removes that.
Try it
export LLM_KEY="<openrouter key>"
node server.mjs # serves localhost:7811
Open the page, type owner/repo, watch the agent work. Full source:
a single server.mjs (agent loop + rotation) and index.html.
If you maintain an open-source repo — point this at your issues for
10 seconds and tell me if the verdicts feel right. Feedback welcome below.
Top comments (0)