Code review on your own Claude plan.
Open a pull request. A runner on your machine reads every push with your own login and posts one verdict as hawkeye[bot]. No token bill, no credentials in the cloud, and silence where the code is fine.
npx hawkeye-review prepare <pr-url>Started 05:57 UTC. This comment is replaced when the review lands.
Blocked
- Round one through the placeholder with any line-anchored finding records the supplemental review as the living review, so round two patches the wrong comment.
- The early superseded/already-posted returns in postReviewForRun never clear the placeholder, leaving a permanently shimmering "Reviewing on" comment.
- Otherwise well-scoped: escaped badge, additive migration, docs in lockstep, good unit coverage of the block and the mark/clear paths.
Must fix
Round one via the placeholder records the supplemental review id as the living review.
apps/web/src/review-posting.ts:292With inline comments,record(supplemental.id)runs and the body is written to the placeholder, but this is the only reviewPosted row, solivingReviewForreturns the supplemental review. Round two then prepends the reviewing line to that marker-only comment and overwrites it, stranding round one's body in the placeholder.59b565718f8dwhy
livingReviewFor orders by postedAt asc; before this PR round one went through the postRenderedReview branch and recorded the id of the review that actually holds the body. The placeholder branch breaks that invariant only when comments.length > 0, which is the common case for a first review with an anchored finding.
Should fix
- The in-transaction superseded and already-posted returns never close the placeholder.
apps/web/src/review-posting.ts:185A newer job that finishes first makes this returnsupersededbeforeliving/placeholderis resolved, so the round-one placeholder keeps the animated "Reviewing on" badge forever. Only the later supersede check (after the token fetch) clears it, and that is the only path tested.ca837f2d66b5
Optional
- Failed or superseded rounds accumulate dead placeholder comments on the pull request.
apps/web/src/runner-api.ts:197A closed placeholder never becomes a living review, so the next claim posts a fresh one; three failed rounds leave three "The review did not complete." comments. Reusing the closed placeholder, or minimizing it, keeps the thread to one.88ec8597af01 - The route test is not colocated with the route it tests.
apps/web/src/reviewing-route.test.ts:2AGENTS.md asks for tests beside the module; this one sits insrc/because the vitest include glob isapps/*/src/**. Widening the glob would let it live next toapp/status/reviewing/route.ts.b9e6dff28126
Review lenses
| Lens | Assessment |
|---|---|
| Intent | The diff does what the body says: a marked block at claim time, a placeholder review on round one, closing sentences on failure and supersede, and the badge route. No unrelated work; the vitest @/ alias and the added review client method are both needed by the feature. |
| Behavior | The happy path holds, but two placeholder paths do not. When round one renders inline comments the reviewPosted row keeps the supplemental id while the body goes into the placeholder, so the living review pointer is wrong from round two on. The transaction's early superseded/already-posted returns skip clearing entirely. |
| Blast radius | Migration 0008 is a nullable column, additive. GitHubClient.review is a new required interface method (every mock updated) and claimJob gains a required controlPlaneUrl. The new /status/reviewing route is public and unauthenticated but only echoes a length-capped, XML-escaped query string, so no injection surface. |
| Verification | Good coverage of the block, escaping, mark/clear branches, the route headers and the claim-time placeholder. The gap is exactly where the bugs are: no test posts a round-one placeholder review that also has inline comments, and none exercises the pre-GitHub superseded return. |
| Fit | Naming, module shape, dependency-free SVG and error-swallow-with-log match the repo. reviewing-route.test.ts lives in src/ while its subject is app/status/reviewing/route.ts, which bends the colocation rule; no comments were added and formatting is machine-owned. |
| Hygiene | Single conventional feat(web): subject, no trailers. README and the design SKILL.md are updated in the same PR, and the follow-up (stale-job sweep leaving a line) is named rather than smuggled in. No secrets or new dependencies. |
Reviewed by Hawkeye on the author's own plan · round 1 · 6 turns
A real review of Hawkeye's own pull request #87, first round, written by Claude Code on the author's laptop. Every line is what the bot posted.
What happens when you open a pull request
Every push queues one job for its head.
Pushes collapse to the latest head, so a busy branch never piles up reviews. The hosted control plane holds only the queue, webhooks and findings.
Your runner claims it and clones the branch.
One process on hardware you own. The control plane never sees a plan credential and never proxies model traffic.
Your coding agent reviews it under your login.
Six lenses, four severities, one verdict. Only validated findings JSON leaves the machine.
The comment lands as hawkeye[bot].
A comment, never a block. On the next push, addressed findings resolve and only new ones are raised.
What it costs
Nothing beyond the plan you already pay for. Your plan's limits are the budget, and the dashboard shows what each review cost in turns and minutes.
Start with one pull request
Install the GitHub App on a repo you admin, start the runner on your machine, open a pull request.
Open source, and reviewed by itself
MIT licensed.
Every pull request to Hawkeye is reviewed by Hawkeye before a maintainer reads it. Read the reviews.
Hosted for you, in preview.
hawkeye-review.vercel.app runs the same code you can read on GitHub. Sign in with GitHub, install the App, and it is yours.
Contribute a lens, a harness, a fix.
Issues and pull requests are open; the contributing guide and the security policy say how.