AI wrote the PR.
You still have to review it.
ReviewStage drafts your review from the real diff, on your own Claude plan, and stages every finding privately. You pick what reaches GitHub, edit it, and post it under your own name. Nothing posts until you click.
git clone https://github.com/Wimukti/reviewstage && cd reviewstage && cp .env.example .env && docker compose up -d @you was requested to review #482
- 00:02 checked out worktree
- 00:05 reading 9 files
- 00:41 walking critical path: payments/refunds/**
- 03:12 6 findings
- high Retry can refund twice on a timed-out capture critical path
- high Idempotency key is regenerated on every retry
- medium Backoff has no jitter
- medium Timeout error is swallowed into a generic 500
- medium Test covers the happy path only
- low RETRY_LIMIT is a magic number
- retry.ts:88 Retry can refund twice on a timed-out charge
- client.ts:41 Idempotency key is regenerated on every retry
- refund.ts:57 Timeout error is swallowed into a generic 500
- retry.ts:9 RETRY_LIMIT is a magic number
Someone asks for your review. Nothing has run yet.
Click-to-run, on your own Claude plan. Never automatic.
Runs on your Claude subscription. Profiles critical paths.
Every finding is staged privately. Nothing is on GitHub.
Keep, edit, or drop. Your judgment, your words.
Posts under your own name. Never as a bot.
- Open source (MIT)
- Self-hosted
- Human-gated
- Runs on your Claude subscription
- Never posts as a bot
The gate
Nothing posts until you click
The review step has no GitHub write path at all. It reads the diff and produces a file the dashboard renders. Every write to GitHub is a separate, deliberate click by a signed-in person, using that person's own token.
Comment-only, by construction.
A wrong nit costs a reply. A wrong approval ships a bug under a real person's name. So the two are different buttons, and the agent can press neither.
- Comment-only. Posting is always a plain COMMENT review. ReviewStage never requests changes and never blocks a merge; the agent's verdict is shown to you as an assessment, nothing more.
- Approval is its own click, pre-filled with LGTM plus a checklist of the blocker and should-fix findings, editable before it goes.
- Every finding's file and line is validated against the actual diff before posting, so GitHub can never reject the whole review because one anchor pointed outside it.
- A failed GitHub read never degrades into a bad post: the fetch checks the response shape, retries once, and refuses on anything odd.
- blockerRefund can be issued twice on a timed-out capture
- should-fixIdempotency key is regenerated on every retry
- should-fixBackoff has no jitter
- should-fixTimeout error is swallowed into a generic 500
- nitTest covers the happy path only
- nitRETRY_LIMIT is a magic number
LGTM — checked the blocker and should-fix items below before approving.
☐ Refund can be issued twice on a timed-out capture
☐ Idempotency key is regenerated on every retry
The staging area
A review is a staging area
Findings arrive as cards, not as comments. You decide which ones deserve your name on them, reword the ones that are almost right, and drop the rest without anyone seeing.
Your name, your call.
The agent writes for a person deciding from its text alone, so the prose has to be readable, and the findings have to be few and confident. What you drop is recorded, and it stops coming back.
- Tick or untick each finding. Edit the body inline with a markdown preview; the original is kept so the learnings loop knows what you changed.
- Each card leads with a plain title and why it matters; the technical detail is collapsed underneath.
- Explain simply: one click rewrites a finding in plain words and adds how to verify it, on your own Claude account.
- Findings can carry a GitHub suggestion block, so the author gets a one-click Apply on the exact replacement.
- The review is flagged stale when the author pushes new commits. It is never re-run for you; you choose when.
- Re-run at any effort or focus. Every earlier run stays in history and can be opened read-only.
A customer in Perth can miss the 5 pm cut-off at 2 pm because the comparison runs in UTC.
Technical detail
isBeforeCutoff() compares Date.now() against a wall-clock hour with no zone. Every call site passes the customer's timezone already.
- const now = new Date(); + const now = zonedNow(customer.timezone); return now.getHours() < CUTOFF_HOUR;
The order deadline is checked on the server's clock. For a shop in another timezone the deadline lands at the wrong hour.
- Set a customer's timezone to Australia/Perth.
- Place an order at 14:30 Perth time.
- Expected: accepted. Current: rejected as past cut-off.
Billing
Runs on your own Claude plan
ReviewStage runs the genuine Claude Code CLI. Each reviewer connects their own Claude account in the browser, and the reviews they start are billed to their plan and bound by their plan's limits.
Honest about the cost.
There is no shared API key and no metered SaaS. Whoever clicks Start pays for that run on their own subscription, and everyone requested on the PR reads the result.
- Click-to-run, not on every push. A full agent run against a 25-file PR takes 10 to 15 minutes and real tokens; you decide which PRs earn one.
- Quick, Standard or Deep effort, auto-suggested from the diff size. Deep tells the agent to search the whole repository for impact before judging.
- A focus note ("pay attention to the cut-off maths") is folded into the prompt on top of the skill.
- Choose the model per run: your plan's default, Opus, Sonnet or Haiku. The PR page shows which model ran and how many tokens it used.
- Runs serialise one at a time per server, and refuse to start below a free-memory floor, so two agents never fight over a small box.
- A re-run of the exact same commit, effort, focus and model is served from cache: zero new tokens.
Team mode
One server, every reviewer, their own name
One instance serves the whole team. Each person signs in once; from then on their queue, their runs, their posts and their approvals are theirs, under their own GitHub identity.
Shared server. Personal accountability.
The design refuses to have a bot identity. If a comment carries your name, you chose it, and the audit trail on GitHub is the audit trail.
- Independent reviews per reviewer, each in its own git worktree, so two people on one PR never collide and can compare what they each found.
- A poller notices review requests within three minutes and sends each requested person their own Slack card. Discord is planned.
- Posts and approvals go out under each person's own GitHub account. GitHub's own self-approval check runs against that user.
- The service token in the server's .env does reads and the base clone only. It cannot post or approve as anyone.
- Stacked PRs (Graphite or ghstack style) can be reviewed from one page, top to bottom, at one effort.
- #482 YMRefund flow: retry on gateway timeout highmedium
- #479 YCut-off check in the customer's timezone medium
- #477 MStacked: extract RefundGateway (2 of 3)
- #475 no run yetBump pnpm and lockfile low
The loop
It learns what your team drops
Every post records which findings were kept, reworded or dropped. Recent decisions are fed into the next review of the same repository, so the agent stops raising what your team has already rejected. Not machine learning: in-context steering with your own recent choices.
A flywheel with a human at every turn.
Usage produces accept and reject signal. Signal shows which skills work. That informs a better team default, edited and approved by people.
- Per-skill keep rate: each review records which skill ran it, so the Skills page shows how often each skill's findings survive a human.
- Bring your own Claude Code skill. Paste your review skill and ReviewStage runs its logic, appending only the output contract the dashboard needs.
- Quick-add a rule in plain words ("don't ask for a ticket link in code comments"); it is tidied into the skill's Team rules section.
- The team default is versioned. Every edit is committed with the editor's name, and the revision history is on the page. It cannot be blanked, and restoring the built-in takes a typed confirm.
- Independence-weighted agreement: when reviewers using a different skill, model or effort raise the same finding, it is marked confirmed. Same configuration twice does not count.
- Team defaultruns by default 71.4% kept · 38 reviews
- Your skill 64.0% kept · 11 reviews
- Built-in 58.3% kept · 20 reviews
Beyond the review
From PR to QA guide
The same server can turn a PR into a tester-ready QA guide: what to set up, what to test first, where the change must not appear, and what not to file as a bug.
Hand testers something grounded.
The guide is markdown you can paste anywhere. It is generated on demand, one job at a time, and can be stopped like a review.
- Risk-tiered P0 / P1 / P2 cases derived from the real diff, the review threads and the branch's commit history, not from the PR description.
- Written for a tester who has never seen the code: numbered steps, explicit pass and fail, no identifiers except the ones a tester has to type.
- Includes a surface matrix with the deliberate "must not appear" rows and a list of known non-defects, so QA does not file them.
- Runs on the requesting user's own Claude account, in its own worktree, never writing to GitHub.
- p0 01Refund after a gateway timeout is issued exactly once
- p0 02Retry reuses the original idempotency key
- p0 03Timed out, then succeeded: no duplicate
- p1 04Backoff spreads retries after an outage
- p1 05Slow gateway is reported as a timeout, not a 500
- p2 09Refund UI unchanged for card payments
- + 3 P1 and 2 P2 cases
Who it's for
One developer, one team, or the whole company
The same container at every size. Start on a laptop, move it to a server when a second reviewer wants in, and add repositories when the organisation does.
-
Solo
Docker on your own machine. Paste a PR URL, get the findings, post the ones you stand behind.
- docker compose up -d and you are in
- No notifications to configure; you open it when you review
- Runs on your own Claude plan, no shared key
- Add to Home Screen and it opens like an app
-
Team
A shared instance every reviewer signs in to. Within three minutes of a review request, the requested person gets a card.
- Review-request alerts to Slack, Discord or any webhook
- Everyone signs in as themselves and posts as themselves
- Independent runs per reviewer; agreement across them is marked
- Skills, learnings and Insights shared by the team
-
Company
One server for the organisation: an allowlist of repositories, per-repo skills and risk paths, and Insights across all of them.
- Multiple repositories per server
- Org membership allowlist for who may sign in
- Per-repo skills and risk paths
- Insights across repositories
- GitHub App sign-in with central revocation roadmap
Same install, same gate at every size: nothing reaches GitHub without a signed-in person clicking, under their own name. Team and Company are configuration, not a different edition.
Ready when you are
Three steps to a staged review
- 1
Bring it up
docker compose up -d and open http://localhost:8899. bin/doctor.sh checks Docker, the .env, and that Claude Code can sign in.
- 2
Sign in as yourself
A fine-grained GitHub token scoped to the repo, then Connect Claude in the browser. Both are stored encrypted and only used for actions you click.
- 3
Paste a PR URL
Pick Quick, Standard or Deep, optionally a focus note and a model, and run. Read the findings, tick, edit, post. Approve is its own click.
Add --profile team for the review-request poller and Slack cards, so teammates are pinged when a PR is waiting on them. Run docker compose up -d demo to see the dashboard with sample runs before wiring anything up. Details in Team mode.
Stage it. Then post it as yourself.
One command to a staged review.
git clone https://github.com/Wimukti/reviewstage && cd reviewstage && cp .env.example .env && docker compose up -d