Skip to content

feat(daemon): a switch to leave team-only review requests to the click - #95

Merged
maxidragon merged 3 commits into
mainfrom
feat/skip-auto-review-team-requests
Nov 4, 2026
Merged

maxidragon merged 3 commits into
mainfrom
feat/skip-auto-review-team-requests

Conversation

@maxidragon

@maxidragon maxidragon commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The inbox poll drafts a review for every PR where GitHub asks for your review. GitHub's review-requested:@me search 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:

  • A PR whose review was requested only from a team you are on is still found, listed and announced, but it waits for your click, just like a repo set to review-on-click. This applies in every repo, whatever the repo's own exception says.
  • A request naming you is drafted either way, even if a team was asked too.
  • If GitHub's team auto-assignment turns the team request into one naming you, the next poll drafts it.
  • The row explains why it is waiting ("Only a team you are on was asked…"), and the queue bar shows team requests on click.

How it works

  • While the switch is off, the poll runs a second search beside the existing one, user-review-requested:@me (searchNamingMe, src/core/gh.ts). markTeamRequests marks any awaiting PR missing from it as requestedViaTeam. gh has 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.
  • A failure of that search doesn't fail the poll. It is logged, and the PRs it couldn't classify are published as unknown (null). With the switch off, those wait for a click too: only a request known to name you is drafted unasked.
  • The daemon status publishes the switch's value together with the awaiting list from the same poll, never before it, so a switch turned off in the cockpit takes effect at the next poll like the other toggles.
  • The mark goes on the daemon's published awaiting list (AwaitingPr.requestedViaTeam). GET /api/reviews copies it onto each row. The daemon's drafting check, the cockpit's bell (isNews) and the row explanation all read that one fact through autoReviewFor, 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.
  • Edge case: the by-name search is capped at 50 results, so if more than 50 open PRs name you, the overflow is read as team-only. When the switch is off, the worst outcome is a draft left to the click rather than one written unasked.
  • --no-auto-review still 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.md and AGENTS.md are updated to match.

Verification

  • pnpm typecheck && pnpm test: 1017 tests pass, pnpm build succeeds.
  • New tests cover the rule (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 from autoReviewFor, 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.
  • Ran both real searches against a live gh login (before they were split into two functions). The by-name/team-only split matched what the two GitHub searches return when run by hand.
  • Not checked by eye: the Settings checkbox in a running cockpit. It mirrors the auto-review checkbox right above it.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread src/core/gh.ts Resolved
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.
@maxidragon
maxidragon requested a review from a team November 4, 2026 14:41

@dawid-walkiewicz dawid-walkiewicz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

🔍 Details

  • autoReviewFor now takes the PR (owner, repo, requestedViaTeam) instead of owner/repo. A true or null mark with the switch off returns false before any per-repo exception is checked.
  • searchNamingMe passes user-review-requested:@me as query text, because gh has no flag for it. markTeamRequests compares 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.

Comment thread src/server/daemon.ts Resolved
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".
@maxidragon
maxidragon merged commit 8c906a4 into main Nov 4, 2026
8 checks passed
@maxidragon
maxidragon deleted the feat/skip-auto-review-team-requests branch November 4, 2026 15:08
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.8.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants