Skip to content

← The log

Will Engwire review this pull request?

The decision has four stages and the order matters — a request can clear every rule and still wait, and queued work is re-judged against the config in force rather than the one that scheduled it.

GuideKonstantin Tarkus

Engwire reviews some of the pull requests that request your review and not others. The decision has four stages, and the order is the part worth knowing: a request can pass every rule you wrote and still not be reviewed, and one already queued can be revoked by a change you make afterwards.

One check sits outside the four and stops all of them. Before it polls, and again before it claims a run, Engwire confirms your gh is still signed in as the account this installation belongs to. If it cannot tell, or the answer has changed, everything holds where it is and the log says so — nothing is ever dismissed for it.

If you are here because something did not happen, start from what you can see:

What you see Where it was decided
No row at all in engwire status First check it has not scrolled off: status lists the fifteen most recent runs, not every one. Otherwise stage 1 — the request predates the watermark, was addressed to a team rather than to you, or was withdrawn between two polls.
A row saying dismissed Stage 2, or a queued run revoked in stage 3. The reason column says which rule it fell to.
A row that stays queued and never moves A hold — and holds look exactly like queued rows. Causes are spread across stages 2, 3 and 4: a draft, a request that stopped appearing, a pull request back in draft, a skill Claude will not run, or a gh that is signed out or signed in as somebody else. The last two say so in the runner’s log; the rest write nothing down anywhere.
A row saying superseded A newer ask for the same pull request arrived while this one waited — stage 3. The newer row is the one to follow.
A review of code that has since moved Retargeting in stage 3 pins at the last poll before the run is claimed.
Two reviews of one pull request Two requests. Each ask carries its own identity — see stage 4.

1. Discovery — what the poll returns at all

Every poll asks GitHub for the open pull requests that currently request your review, filtered to those newer than a watermark. A request addressed to a team you belong to is not one of them: Engwire acts only on asks that name you directly.

The watermark is set the moment a runner first starts with a rule configured — not at install, and not at setup. A request older than it never reaches any of the stages below, which means it gets no rule check, no dismissal, and no row anywhere. It simply is not there. So the one already sitting in your queue is not the one to expect back: start Engwire once to set the mark, then ask for a review.

There is one watermark, not one per rule, so adding a rule does not reach back past it.

What decides whether a later rule can pick something up is not the rule’s age but whether Engwire has already had an opinion about the request. One that arrived after the mark while the runner happened to be stopped was never seen, never recorded, and is still fair game if it is outstanding. One that was seen and passed over is closed for good — the reasoning for that is at the end of stage 2.

Two consequences of polling rather than listening. A request made and withdrawn between two polls may never be seen at all. And what the poll returns is the ground truth for the next stage, not just for new work.

2. A request seen for the first time

Four questions, in this order — and the order decides which reason you see.

Is there a newer ask for the same pull request in this poll? Then the older ones are recorded as replaced, before anything else is asked about them. An older ask for a repository no rule names is reported as replaced, not as having no rule.

Does a rule name the repository? repos takes exactly three shapes — acme/payments, acme/*, or * — and a pattern Engwire cannot read is an error rather than a rule that matches nothing. So is a key it does not recognise: skip_draft = false will not quietly leave drafts skipped. The first matching rule wins, so the specific one goes above the general one, and getting that backwards is an error too.

Is it from a fork? Then no, always, with no flag to allow it. A rule names the base repository — the one you trust. Anyone can open a pull request into it, and matching a rule says the change is proposed somewhere you trust, not that it came from someone you trust.

Is it a draft? Then it is held, not dismissed, and taken up when the pull request is marked ready without you asking again. skip_drafts = false reviews them as they arrive.

A dismissal here is permanent for that request. Adding a rule later does not resurrect one already seen and passed over — a surprise review of a month-old pull request is worse than a missed one you can re-request. That is the other half of the watermark exception above: a request the runner never saw can be picked up by a rule added later, and one it saw and dismissed cannot.

3. A run already queued, re-judged

Queued work is judged against the configuration in force at each poll, not the one that scheduled it.

The repository is no longer named by any rule → the run is revoked. repos is an authorization boundary: removing a repository must not be followed by a review of it that was queued under the old configuration. It is recorded with the same reason a fresh request from that repository would get, deliberately, so that the two spellings of “no rule names it” cannot drift apart — in status a revoked run and a never-started one look the same, which is the truth about both.

A fresh ask for the same pull request has appeared → the queued run is superseded by it, before any of the checks below are reached. A held draft counts: it will not run yet, but it is the live request now. A permanently dismissed one does not — it is going nowhere, and dropping the queued run for it would lose the review altogether.

It has stopped appearing in the poll → held. Withdrawing the request, closing the pull request, or simply reviewing it yourself is enough to stop a run that has not begun. Held rather than dismissed because a missing request may be nothing worse than GitHub’s search index lagging. If that is all it was, the hold costs one poll. If the request is really gone, the run stays queued indefinitely — the queued-forever row in the table above. Either is cheaper than a wrong dismissal, which costs a review nobody can ask for again.

The head moved, or the rule now names a different skill → the run is retargeted to the current head and today’s skill. Pinning still happens; it happens at the last poll before the run is claimed, so a review that waited through three pushes reviews what is there now rather than posting comments on code that has moved.

It has been converted back to draft → held. Not dismissed, because the request is already recorded and GitHub may never re-request you once it is ready again — consuming it here would lose the review for good.

4. Claiming — one at a time, and the skill has to exist

One review runs at once. There is no setting for a second.

Before the next run is claimed, its skill is checked: missing, unreadable, named synced, or not left invocable by its own front matter all hold it. That is a broken local setup rather than a failed review, and it has to be caught before the run is claimed because of how it would otherwise fail: Claude Code exits 0 on a slash command it does not recognise, so the runner would read success, mark the request handled, and spend a review request on nothing. Held instead, the run stays queued. Per run, not per config: one misspelled rule holds the repositories it names and nothing else.

Once claimed, a review runs at most once for that request. Each ask carries its own identity from the pull request’s issue events, and the database holds a uniqueness constraint on it — so re-requesting your review gets you a second one, because you meant it. A review is never retried: it posts to GitHub, and Engwire cannot know what has already been said there.

What you can see

engwire status records dismissals with their reason:

2h ago   dismissed   acme/www#96    Bump dependencies    opened from a fork

Those three are the complete set — no matching rule, opened from a fork, replaced by a newer request. There is no fourth spelling, and a revoked queued run reuses the first of them rather than adding one.

A held run is deliberately indistinguishable from a queued one in that table. Some holds explain themselves in the runner’s log — a held skill names the skill and what is wrong with it — but a hold from reconciliation writes nothing durable at all, so a run held because it stopped appearing in the poll or went back to draft is a queued-looking row with no recorded reason anywhere. And a request filtered out at stage 1 has no row at all, because nothing ever recorded it.

README →

Engwire reviews the pull requests that ask for you, on your own machine.

MIT · macOS and Linux · no account to make

curl -fsSL https://engwire.com/install.sh | sh

What Engwire does →