Skip to content

NAS-144026 / 27.0.0-BETA.1 / Request a team review when a human review is needed - #19

Open
william-gr wants to merge 3 commits into
masterfrom
request-human-review
Open

william-gr wants to merge 3 commits into
masterfrom
request-human-review

Conversation

@william-gr

@william-gr william-gr commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When the verdict is "needs a human review", the workflow now requests a review from a team in the calling repository's org. The team's code review auto-assignment picks who gets it.

  • New input human-review-team, default ux-team. Empty disables the request. ux-team exists in truenas-connect, iXsystems and truenas, with write on all five consumer repos.
  • Once per PR. Skipped if any reviewer is already requested, or if an earlier verdict on the PR recorded a request, so each push does not assign someone new.
  • No team ping. The review body names the team in backticks, not as an @-mention, which would notify every member.
  • Best effort. A refused request logs a warning; the verdict and exit status are unchanged.

Verified against a mocked API: first request, skip when a reviewer is already requested, skip after an earlier request, refused request, empty input, and no request on the approve path.

Needs doing outside this PR

  • Turn on code review auto-assignment in ux-team's settings in each of the three orgs. Without it the whole team stays requested.
  • It needs github-token. This PR's first self-review confirmed the job token cannot request team reviewers: GitHub answered 422, "Could not resolve to a node with the global id" of the team. Without a github-token the request is skipped with a log line. A fine-grained PAT may also need organisation Members read; that part is unverified.
  • CODEOWNERS repos gain little. truenas-connect/ui and truenas/webui already name ux-team for every file, so GitHub requests the team on every PR and this request is skipped. It adds value in truenas-file-manager, truenas-ui-components and api-client-ts.

No Jira ticket on hand for the title; add one so PR ticket passes.

The needs-a-human verdict now asks the calling org's team (default
ux-team) for a review; the team's auto-assignment picks the person.
Once per PR: skipped when any reviewer is already requested, or when an
earlier verdict recorded a request, so pushes do not reassign it. Best
effort, and the body names the team without an @-mention so only the
assigned reviewer is notified.
Comment thread review/submit-verdict.mjs
const team = `${repo.split('/')[0]}/${humanReviewTeam}`;
try {
const requested = await api(`/pulls/${number}/requested_reviewers`);
if (requested.users?.length || requested.teams?.length) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — This guard also disables the feature outright in any consumer with a CODEOWNERS file. GitHub auto-requests code owners when the PR opens, so requested.users/requested.teams is already non-empty on the first verdict run and the team is never asked. The comment above frames the guard as being about pushes not reassigning ("so each push does not assign someone new"), and README's own CODEOWNERS bullet a few lines below the new one documents that configuration as supported — so a reader of either would not expect the request to be skipped from the start.

Nothing produces a wrong result (someone is already requested, which is arguably the point), so this is LOW rather than a bug. Worth a sentence in the README bullet, or narrowing the guard to requested.teams plus reviewers the workflow itself requested, if the intent is that ux-team gets asked even when code owners already were.

Comment thread review/submit-verdict.mjs Outdated
console.log('A reviewer is already requested; not requesting the team.');
return [];
}
const reviews = await paginate(`/pulls/${number}/reviews`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — /pulls/{n}/reviews is now paginated twice on the needs-a-human path: here, and again in dismissOwnStateReviews a few statements later (line 232), which runs immediately after the submit('COMMENT', …) on the same path. Same endpoint, same PR, up to 30 pages each, and the second call is the only one that needs to be after the new review exists.

Fetching once before requestTeamReview and passing the list into both would halve it. Not a correctness problem — just duplicated API work on the busiest path.

@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Three findings: three LOW. Nothing blocks. A human should look at this one.

The mechanism itself holds up. requestTeamReview returns [] on every path
that must not request — empty input, no github-token, a reviewer already
requested, an earlier verdict carrying REQUESTED_MARKER — and its try
covers both API calls, so a refused request is a ::warning:: and never the
exit status. The reviewsCache added at line 91 is safe as used: it is filled
before the verdict is posted only on the human-review path, and
dismissOwnStateReviews already excludes the new review by id, so the
snapshot missing it changes nothing. The MARKER-plus-REQUESTED_MARKER test
short-circuits on a null body. The POST sends the bare slug, which is what
team_reviewers wants; org/slug appears only in the log and the body, in
backticks rather than as an @-mention.

HAS_GITHUB_TOKEN: ${{ secrets.github-token != '' }} agrees with
GH_TOKEN: ${{ secrets.github-token || github.token }} on the same step — an
unpassed optional secret is empty on both, so the flag cannot say "PAT" where
the token is the job's. Skipping rather than attempting is the right call: with
the job token the request is a guaranteed 422, and a warning on every
consumer's needs-a-human PR would train people to ignore the warnings.

Findings

  • LOW — review/submit-verdict.mjs:12 — the header outcome table says the
    team review is requested "once per PR" with no mention of the
    github-token condition; without one it is never requested, which is the
    default for every consumer and for this repo's own self-review. The README
    row was qualified in d291001, this comment was not.
  • LOW — review/submit-verdict.mjs:334 — the once-per-PR record is written
    by the review posted after the request, so a request that lands while the
    review does not leaves no marker; if the assigned reviewer then reviews
    before the next push, both guards are open and a second person is assigned.
  • LOW — README.md:329 — the auto-assignment bullet is under "Making the
    review count is branch protection, per repo", but it is an org team setting,
    not branch protection and not per repo.

Needs a human review. human-review-team defaults to ux-team, so this
adds a write the workflow did not previously make — POST /pulls/{n}/requested_reviewers against a caller's repository — and turns it on
for every consumer at @master that passes a github-token, with no opt-in
of their own. The blast radius is people being assigned work in three orgs, and
the PR body lists org-side setup (auto-assignment per team, possibly Members
read on the PAT) that has to be in place for the request to land on one person
rather than the whole team. That is a decision to make deliberately, not one to
discover from a notification.

The unresolved thread at review/submit-verdict.mjs:275 is left where it is;
this push did not change that line. check-ticket is red for the reason the PR
body gives — the title carries no ticket — which is not a finding on the diff.

@github-actions github-actions Bot 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.

Needs a human review. 2 finding(s): 2 LOW.

Nothing blocks, but this change is one a person should decide on:

  • .github/workflows/claude-review.yml:131 sets default: 'ux-team', a caller-visible default that opts every @master consumer into requesting reviewers from real people with no change on their side

…uard

The self-review run confirmed the job token cannot request team
reviewers (422, "Could not resolve to a node"), so the request is now
skipped with a log line unless a github-token is passed, rather than a
warning on every needs-human run.

The guard stays on any requested reviewer: truenas-connect/ui and webui
name ux-team in CODEOWNERS, and after auto-assignment replaces the team
with a user, a team-only check would assign a second person. README says
so. Reviews are fetched once on the needs-human path.
Comment thread README.md Outdated
|---|---|---|
| Anything at MEDIUM or above | REQUEST_CHANGES | fails |
| Only LOW or none, a human must look | COMMENT listing the reasons | passes, no approval |
| Only LOW or none, a human must look | COMMENT listing the reasons, and a review request to `human-review-team` | passes, no approval |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — This row promises the review request unconditionally, but the request only happens when a github-token is passed, no reviewer is already requested, and no earlier verdict recorded one. github-token is optional and defaults to empty, so for a caller on the default configuration this row describes something that never occurs — the log line "Not requesting …: the job token cannot request team reviewers" is the whole behaviour. The input table and the branch-protection prose both state the condition; only this summary row omits it. A qualifier such as "and, with a github-token, a review request to human-review-team" would make the three agree.

Comment thread review/submit-verdict.mjs Outdated
return all;
};

// Fetched before the verdict is posted; every consumer excludes that review by id.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — Both halves of this comment are narrower than the code. The cache is fetched before the verdict only on the needs-a-human path, where requestTeamReview() primes it; on the approve-when-clean: false and refused-APPROVE paths dismissOwnStateReviews is the first caller and fetches after submit(). And "every consumer excludes that review by id" is not true of requestTeamReview, the consumer this cache was added for — it matches on the two body markers and never looks at id. It does not need to, precisely because it runs first.

Nothing misbehaves today; the hazard the comment guards against is real but stated as an invariant that the file does not hold. Something like "may be populated before the verdict is posted, so callers that must not see it exclude it by id" would describe what is actually here.

@github-actions github-actions Bot 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.

Needs a human review. 2 finding(s): 2 LOW.

Nothing blocks, but this change is one a person should decide on:

  • .github/workflows/claude-review.yml:129 defaults human-review-team to ux-team, so the four repos calling this at @master start requesting reviewers with no change on their side.

Comment thread review/submit-verdict.mjs
Comment on lines +12 to +13
* clean, but a human must look COMMENT naming why, team review
* requested once per PR, job passes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — This outcome table now says the human-review path requests a team review "once per PR", unqualified, but requestTeamReview returns early without a github-token (line 269) — which is the default for every consumer, including this repo's own claude-review-self.yml, which maps no github-token. The README's equivalent table row was qualified with "and with a github-token" in d291001; this header comment is the same claim left unqualified.

Suggested change
* clean, but a human must look COMMENT naming why, team review
* requested once per PR, job passes
* clean, but a human must look COMMENT naming why, and with a
* github-token a team review
* requested once per PR, job passes

Comment thread review/submit-verdict.mjs
// On the flag, not the reason count: `required: true` with no reasons is
// schema-valid and must not approve.
if (humanReview.required || reasons.length > 0) {
const requestLines = await requestTeamReview();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — The "once per PR" record is written by the review posted after the request, so a request that lands while the review that would record it does not leaves no trace of itself.

Sequence: run A requests the team, then submit('COMMENT') throws (a dropped connection, a 5xx, the 15s AbortSignal.timeout) and the outer catch at line 383 turns it into a warning — no review body carries REQUESTED_MARKER. Auto-assignment swaps the team for Alice, Alice submits a review, and GitHub clears her from requested_reviewers. The next push reaches run B with both guards open: the requested list is empty and no review carries the marker, so it requests the team again and a second person is assigned.

Narrow, and the feature is best effort by design, so this is not worth much — but it is the one case the marker exists to cover and does not. Requesting after the review is posted, with the marker in the body either way, would close it.

Comment thread README.md

Making the review count is branch protection, per repo:

- **Turn on code review auto-assignment for the team** named by

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOW — This bullet sits under "Making the review count is branch protection, per repo:" (line 327), but it is neither: code review auto-assignment is an org team setting, configured once per org rather than per repo, and it does not make the review count towards anything branch protection checks. Every other bullet in the list is a repository branch-protection or Actions setting. A reader following the section heading to their repo's protection rules will not find it there.

It reads as its own paragraph before the list — something like "The team request needs one setting outside this repo:" ahead of it, with the branch-protection list starting at Require 1 approval.

@github-actions github-actions Bot 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.

Needs a human review. 3 finding(s): 3 LOW.

Nothing blocks, but this change is one a person should decide on:

  • .github/workflows/claude-review.yml:131 - human-review-team defaults to ux-team, turning reviewer requests on for every consumer at @master with a github-token, with no opt-in
  • review/submit-verdict.mjs:284 - adds a write the workflow never made before, POST /pulls/{n}/requested_reviewers, assigning work to people in three orgs

@bugclerk bugclerk changed the title Request a team review when a human review is needed NAS-144026 / 27.0.0-BETA.1 / Request a team review when a human review is needed Sep 22, 2026
@bugclerk

Copy link
Copy Markdown
Contributor

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