← back to gallery

pr-bounce

Live PR rejection-rate leaderboard for AI coding agents — and why their PRs get bounced.

dev-toolsai-agentsgithubleaderboardcode-reviewclaude-codecopilotcursor
Open product ↗

pr-bounce

Live PR rejection-rate leaderboard for AI coding agents on public GitHub. Claude Code, GitHub Copilot, Cursor, OpenAI Codex, Devin, Aider, Gemini CLI, Cline, ShioriCode — when their pull requests don't merge, why don't they?

A public dashboard that ranks each major AI coding agent by what fraction of its public-GitHub pull requests actually get merged — and, for the ones that didn't, clusters the maintainer's closing comment into a structured rejection-reason taxonomy using Claude Haiku 4.5.

Live: https://holyai.me/pr-bounce/
Stack: Node.js 20 · Express · better-sqlite3 (WAL) · node-cron · helmet · compression
Port: 4816 · BASE_PATH: /pr-bounce · Auth: none — every endpoint is public.

Why now

Agentic coding has eaten the world. Claude Code's adoption arc, Copilot's full migration to agent mode in 2026, Cursor's PR-authoring agents, OpenAI Codex's codex/... branch workflow, Devin's GitHub bot, Aider, Gemini CLI, Cline, ShioriCode — they all push PRs into public repos in volumes that were impossible 18 months ago. GitHub finally added Copilot PR merge metrics to the usage-metrics API in April 2026, and individual vendors publish their own acceptance rates. But there is no vendor-neutral, side-by-side public dashboard answering the obvious question:

Of the PRs an AI agent opens, what percentage actually get merged — and when they don't, why?

pr-bounce is that dashboard. It is the rejection-side complement to agent-bloat (PR size, merged-only) — and it adds an LLM-classified reason taxonomy on top.

What it shows

Where the data comes from (no mocks)

Every numeric value is derived at runtime from public endpoints.

| Endpoint | Used for | Cadence |
|---|---|---|
| GET api.github.com/search/issues?q=is:pr "<signature>" | Discover AI-attributed PRs regardless of state | every 30 minutes, one agent per slot |
| GET api.github.com/repos/{owner}/{repo}/pulls/{number} | Authoritative state, additions, deletions, files changed | on first sight & after open-stale poll |
| GET api.github.com/repos/{owner}/{repo}/issues/{number}/comments | Last maintainer comment near close, for the reason classifier | once per newly-closed-unmerged PR |
| GET api.github.com/rate_limit | Rate-limit header surfaced on /health | once per refresh |
| POST api.anthropic.com/v1/messages (Claude Haiku 4.5) | Classify closing comment into one of 7 reason classes | hourly batch, only if ANTHROPIC_API_KEY is set |

If a refresh discovers zero PRs for an agent, that row is rendered empty and fetch_log records status: "empty". The app does not invent numbers. Without ANTHROPIC_API_KEY (or OPENROUTER_API_KEY fallback) the classifier is silently disabled — PRs are still indexed; the reason badge just doesn't appear.

Detection signatures

Detection is a case-insensitive substring match on the PR body + commit trailers. Priority order: claude-code > copilot > cursor > codex > devin > aider > gemini-cli > cline > shioricode. Signatures live in lib/agents.js.

| slug | signal |
|---|---|
| claude-code | Co-Authored-By: Claude <[email protected]> / Generated with [Claude Code] |
| copilot | Co-authored-by: Copilot <[email protected]> / co-authored-by: copilot-swe-agent |
| cursor | Co-authored-by: Cursor / https://cursor.com/agents |
| codex | Co-authored-by: openai-codex / Generated with OpenAI Codex |
| devin | devin-ai-integration[bot] |
| aider | Co-authored-by: aider / aider.chat |
| gemini-cli | Co-authored-by: gemini-cli / gemini-code-assist[bot] |
| cline | Co-authored-by: cline / Generated with [Cline] |
| shioricode | Co-authored-by: shioricode |

Reason taxonomy (7 classes + other)

A fixed prompt at lib/classifier.js asks Claude Haiku 4.5 for one of:

  1. hallucinated — references nonexistent APIs, methods, files, or dependencies.
  2. out-of-scope — solves the wrong problem.
  3. over-engineered — unnecessary abstraction or refactor.
  4. missing-tests — no tests, broken tests, or coverage gaps.
  5. style-conflict — lint, format, naming, or convention violations.
  6. duplicate-or-superseded — duplicate of another PR / superseded.
  7. wontfix-or-not-planned — maintainer rejects the premise.

Anything that does not clearly fit one of the seven → other, with Haiku's verbatim reasoning kept in reason_notes.

API surface (all public, no auth)

GET  /pr-bounce/health
GET  /pr-bounce/api/agents
GET  /pr-bounce/api/leaderboard?window=7d|30d|90d|all
GET  /pr-bounce/api/recent?agent=<slug>&state=any|merged|closed|open&limit=50
GET  /pr-bounce/api/bounces?limit=50
GET  /pr-bounce/api/agent/:slug?window=...
GET  /pr-bounce/api/repo/:owner/:repo
GET  /pr-bounce/api/by-repo?window=...&limit=20
GET  /pr-bounce/api/reasons?window=...
GET  /pr-bounce/api/trend?agent=<slug>&days=30
GET  /pr-bounce/api/badge?agent=<slug>
GET  /pr-bounce/api/fetch-log?limit=30
GET  /pr-bounce/api/rate-limit
POST /pr-bounce/api/refresh

JSON unless explicitly SVG. CORS is open. /pr-bounce/health is auth-free and returns 200 as long as the process is up.

Cron schedule

| schedule | task |
|---|---|
| /30 | discover one agent's PRs (rotates through 9 agents) |
| 7
| re-poll open PRs whose state we haven't refreshed in 4h+ |
| 21
| fetch closing comments for new closed-unmerged PRs |
| 41
* | classify a batch of pending comments with Haiku |
| +5s after boot | one discover tick so a fresh container has data within a minute |

Embeddable badge

![claude pr-bounce](https://holyai.me/pr-bounce/api/badge?agent=claude-code)

Available slugs: claude-code, copilot, cursor, codex, devin, aider, gemini-cli, cline, shioricode.

Colour bands: ≥60% merge = green, 40–59% = amber, <40% = red, no data = grey.

Run locally

cp .env.example .env
npm install
npm start
# open http://localhost:4816/pr-bounce/

Health: curl http://localhost:4816/pr-bounce/health

Stack

License

Internal / proprietary — part of the Holy AI product gallery.