Repository navigation
feat(daemon): a switch to leave team-only review requests to the click - #95
Conversation
GitHub's review-requested search matches a request made of you by name and one made only of a team you are on, and the poll drafted both. A team request means "someone on this team", so drafting every one of them spends runs on PRs a teammate will usually pick up. Discovery now runs a second search, user-review-requested:@me, and marks every awaiting PR it lacks as requested only through a team. The mark is published on the daemon's awaiting list and copied onto each review-list row, so the poll, the cockpit's bell and the row's explanation decide from one fact. daemon.autoReviewTeamRequests (default true, toggle in Settings) decides whether those PRs are drafted. Off, they are listed and announced like a repo set to review-on-click, in every repo; a request naming you is drafted either way, and a team request GitHub's auto-assignment turns into one for you is drafted on the next poll.
There was a problem hiding this comment.
🟡 Changes recommended
A failed by-name lookup aborts the entire inbox poll, preventing queue synchronization and announcements even when the primary search succeeds.
1 open issue
What changed in this PR
Adds an on-by-default setting that lets users leave team-only review requests for manual drafting while keeping them visible in Cerber’s inbox.
Changes:
- Distinguishes team-only requests from requests naming the user through an additional GitHub search.
- Shares that classification across automatic drafting, notifications, and queue explanations.
- Adds a Settings switch, regression coverage, and updated documentation.
| File | Description |
|---|---|
| web/src/types.ts | Adds team-request configuration and status fields. |
| web/src/Settings.tsx | Adds the team-request drafting switch. |
| web/src/Queue.tsx | Shows when team requests require a click. |
| web/src/notify.ts | Applies team-request policy to notification timing. |
| web/src/notify.test.ts | Covers team-request notifications and compatibility defaults. |
| web/src/inbox.ts | Explains why team-only requests remain awaiting. |
| web/src/inbox.test.ts | Tests the team-request waiting explanation. |
| src/server/index.ts | Includes request classification in queue responses. |
| src/server/daemon.ts | Publishes classification and applies the drafting policy. |
| src/server/daemon.test.ts | Tests classification, drafting, notifications, and reassignment. |
| src/server/create.test.ts | Updates discovery fixtures with request classification. |
| src/core/gh.ts | Adds the by-name search and classifies awaiting PRs. |
| src/core/gh.test.ts | Tests search arguments and classification. |
| src/core/config.ts | Defines the enabled-by-default setting. |
| src/core/config.test.ts | Updates expected configuration defaults. |
| src/core/autoreview.ts | Applies the team restriction across repository settings. |
| src/core/autoreview.test.ts | Tests defaults, overrides, and CLI restrictions. |
| SPEC.md | Documents configuration, discovery, and API behavior. |
| README.md | Introduces the team-request setting. |
| docs/lifecycle.md | Documents drafting eligibility and live configuration. |
| AGENTS.md | Updates the daemon architecture description. |
Review effort: Balanced
The by-name search ran in the same Promise.all as the awaiting search, so a timeout or rate limit on it failed the whole poll: no queue sync, no drafts, no announcements — even with team requests drafted, where its answer changes nothing. It now runs only while autoReviewTeamRequests is off, beside the awaiting search the way the own-PR search does: a failure is logged and the poll goes on. The PRs it could not classify are published as unknown (null) rather than as team requests, and the switch treats unknown like a team's request, so turning it off still means only a request known to name you is drafted unasked.
dawid-walkiewicz
left a comment
There was a problem hiding this comment.
Summary
Adds a setting that stops cerber from writing a review on its own for a PR when GitHub asked only one of your teams, not you by name. It is on by default, so nothing changes until someone turns it off.
🎯 Team review requests filled the queue with drafts nobody read
GitHub's "review requested from me" search also returns PRs where only a team you belong to was asked. Cerber drafted all of them. A team request usually means "anyone on the team", and often a teammate takes it. That spent AI runs and filled the queue with drafts you would never open.
🔧 Ask GitHub a second, narrower question
With the switch off, each poll also runs GitHub's "asked me by name" search. Any awaiting PR missing from that answer counts as team-only. The poll then lists and announces it but leaves the draft to your click. If the second search fails, the poll carries on, and the PRs it couldn't sort out also wait for a click.
🧭 What changes for users
Merging changes nothing until someone unticks the new checkbox in Settings. After that, PRs asked only of a team still show up in the queue and still trigger a notification when they arrive, but no draft is written for them. The row explains why it is waiting. A PR that names you is drafted as before, even if a team was asked too. --no-auto-review still stops every run.
| Switch on (default) | Switch off | |
|---|---|---|
| PR asking you by name | drafted | drafted |
| PR asking only your team | drafted | listed, announced, waits for a click |
| Couldn't tell which | drafted | waits for a click |
| Queue status bar | unchanged | adds "team requests on click" |
🧩 How the pieces fit
Modules. The daemon (the background poller) adds the "team-only" mark to each entry on its published list of awaiting PRs. The review-list API copies that mark onto each row. The daemon's drafting check, the cockpit's bell and the row explanation all read it through the same rule function. The mark is never saved in the review file, so a review run can't overwrite it.
flowchart TD
GH["GitHub search"] -->|"awaiting PRs + PRs naming you"| D["Daemon poll"]
D -->|"awaiting list, each marked team-only or not"| S["Daemon status"]
S -->|"mark"| R["Drafting rule (autoReviewFor)"]
S -->|"mark copied per row"| API["GET /api/reviews"]
API -->|"rows"| C["Cockpit: bell and row explanation"]
C -->|"same rule"| R
R -->|"draft or wait"| D
🔍 Details
autoReviewFornow takes the PR (owner,repo,requestedViaTeam) instead of owner/repo. Atrueornullmark with the switch off returns false before any per-repo exception is checked.searchNamingMepassesuser-review-requested:@meas query text, becauseghhas no flag for it.markTeamRequestscompares results by artifact id.- With the switch on, the second search is skipped and every entry is published as
null, which the rule ignores. - A row the poll did not return, such as your own PR or one you pulled in by hand, reads as
false(not team-only), so it follows the older rules. - Tests cover the rule, the marking, the daemon leaving a team PR undrafted and announcing it, drafting it once it names you, the failed-search path, the bell's timing and the row text.
⚠️ Worth knowing
- The by-name search returns at most 50 results. Past 50 PRs that name you, the extra ones count as team-only. That leans toward not drafting, so the worst case is one draft left to your click.
- Older servers don't send the new fields. The cockpit then assumes team requests are drafted, which is what those servers do.
Walkthrough
The setting and the drafting rule
Adds the autoReviewTeamRequests config field (default on) and teaches autoReviewFor to leave a team-only or unclassified request to the click when the setting is off, whatever the per-repo exception says. capAutoReview turns it off too, so --no-auto-review still stops everything.
Telling team requests apart during the poll
A second GitHub search for PRs that name you, and a pure function that marks the rest as team-only. The daemon runs it only while the switch is off, keeps polling if it fails, and puts the mark on the published awaiting list where its drafting check reads it. Worth checking: the list is written before drafting starts, so the check sees this poll's answer.
Cockpit: switch, status bar, row text and bell
A Settings checkbox, a status-bar note, a row explanation that names the team switch, and the browser bell using the same rule per row. Fields an older server doesn't send fall back to the old behaviour.
Docs and spec
SPEC.md, docs/lifecycle.md, README and AGENTS.md now describe the setting, the second search and what happens when it fails.
Reviewed with cerber 🐕 — drafted by AI, sent by a human.
Turning autoReviewTeamRequests off from the cockpit published the new value at once, beside an awaiting list the last poll had left unclassified (it ran with team drafts on, so every entry was null). Until the next poll every waiting row read "could not tell whether you or only a team was asked", and the browser bell could announce those rows as arrivals and then again when their drafts landed. The status now writes the switch only where it writes the awaiting list, so the two always describe the same poll and the change takes effect at the next tick, like the other daemon toggles. The row's explanation also names the repo's own setting first: with auto-review off for the repo, or capped by --no-auto-review, who was asked changes nothing, and the row said "could not tell" instead of "Auto-review is off".
|
🎉 This PR is included in version 1.8.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |

Problem
The inbox poll drafts a review for every PR where GitHub asks for your review. GitHub's
review-requested:@mesearch counts a request made only of a team you are on the same as one naming you, so team requests get drafted too. A team request means "someone on this team". A teammate often picks it up, so drafting all of them spends runs and fills the queue with drafts you won't read.Solution
A new setting,
daemon.autoReviewTeamRequests, with a switch in Settings. It is on by default, so nothing changes until you turn it off. When it is off:team requests on click.How it works
user-review-requested:@me(searchNamingMe,src/core/gh.ts).markTeamRequestsmarks any awaiting PR missing from it asrequestedViaTeam.ghhas no flag for this qualifier, so it is passed as query text. With the switch on (the default), the answer changes nothing, so the search isn't run at all.null). With the switch off, those wait for a click too: only a request known to name you is drafted unasked.AwaitingPr.requestedViaTeam).GET /api/reviewscopies it onto each row. The daemon's drafting check, the cockpit's bell (isNews) and the row explanation all read that one fact throughautoReviewFor, which now takes the PR rather than just owner/repo. The mark is not stored on the review file, so a review run can't wipe it.--no-auto-reviewstill stops all drafting, team requests included.SPEC.md(§6.1, §6.3, §6.4, §9.1, §9.7, §14.1, API table),docs/lifecycle.md,README.mdandAGENTS.mdare updated to match.Verification
pnpm typecheck && pnpm test: 1017 tests pass,pnpm buildsucceeds.autoreview.test.ts), the two-search marking (gh.test.ts), the daemon leaving a team request undrafted and announcing it, drafting it by default, and drafting it once it names you (daemon.test.ts), the bell's timing (notify.test.ts), and the row explanation (inbox.test.ts). With the team check removed fromautoReviewFor, three of them fail. Two daemon tests cover a failed by-name search (the poll goes on; the row waits for a click), and both fail if its error handling is removed. Another test fails if the switch is published ahead of the list it classified.ghlogin (before they were split into two functions). The by-name/team-only split matched what the two GitHub searches return when run by hand.