Skip to content
Live demo

See PR Quorum before you install.

Explore the dashboard with realistic sample data or read a complete panel review exactly as it appears on a pull request. No sign-up or installation required.

Interactive · no signup

Click around the real dashboard.

The exact UI your team uses — live KPIs, the review feed, reviewer panel, model routing, and spend — loaded with realistic sample data. Take the guided tour or wander on your own.

DashboardRepositoriesReviewsReviewersModelsUsage
app.prquorum.com/dashboard
Reviews
87
Findings
312
Accepted
74%
Reviews per day
Sample review

See exactly what PR Quorum posts.

This fictional billing pull request shows three specialist reviewers running in parallel, with their findings deduplicated and ranked into one review.

github.com/acme-payments/api/pull/2841 · fix: per-seat billing webhookspr-quorum[bot] commented
needs attention3 reviewers, 4 findings · 00:42 · $0.012

This pull request changes 9 files: 6 application files, 2 test files, and 1 configuration file. The panel flagged one critical security gap on the webhook retry path and a billing race; the rest is non-blocking.

critical94%Security
Webhook signature verification skipped on retry path
src/billing/webhooks.ts:142

The retry branch re-processes the raw payload without calling verifyStripeSignature() — only the first-attempt path validates. A forged POST to the retry endpoint would be accepted and mutate subscription state. Move verification above the attempt branch so every path is covered.

high86%Correctness
Race condition between webhook handler and seat refresh
src/billing/sync.ts:73

refreshSeats() reads seat_count, then writes it back after an await on the Stripe API. A concurrent customer.subscription.updated webhook between the read and the write silently loses one of the two updates. Use an atomic UPDATE … SET seat_count = excluded.seat_count or take a row lock for the read-modify-write.

suggested change · committable
- const current = await getSeatCount(accountId);
- await stripe.subscriptions.update(subId, { quantity: current + 1 });
- await setSeatCount(accountId, current + 1);
+ await db.rpc('increment_seats_atomic', { account_id: accountId });
+ await stripe.subscriptions.update(subId, { quantity: await getSeatCount(accountId) });
high79%Architecture
Stripe client constructed inside the request handler
src/billing/index.ts:21

new Stripe(…) per request re-parses the key, re-creates the agent pool, and defeats keep-alive. Hoist it to module scope (the SDK is stateless across requests) — this is also where a request-scoped client quietly leaks API version overrides across handlers.

medium68%Correctness
Idempotency key derived from event.id only
src/billing/webhooks.ts:89

Stripe replays the same event.id on automatic retries, but a manually re-sent event from the dashboard mints a NEW id for the same logical change — the dedupe table won’t catch it. Key on (event.type, subscription.id, created) for state-transition events, or accept the rare double-apply and make the handler idempotent on content.

PR Quorum · 3 reviewers, one verdict · advisory— your team decides what merges · @prquorum review reruns the review · @prquorum pause or @prquorum resume this pull request

Get the same focused review on your next pull request—in about 40 seconds.

No credit card required · Advisory only; never blocks a merge