diff --git a/README.md b/README.md index d60e18c..84f3057 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ cannot disagree with the check: | Only LOW or none, a human must look | COMMENT listing the reasons | passes, no approval | | Only LOW or none, `approve-when-clean` off | COMMENT "would approve" | passes | | Only LOW or none, `approve-when-clean` on | APPROVE | passes | +| Only LOW or none, but GitHub refuses the APPROVE | COMMENT quoting the refusal | passes | | No or unparseable output | nothing | fails | "A human must look" is the reviewer's own answer to the *Does this need a @@ -335,9 +336,12 @@ Making the review count is branch protection, per repo: re-reviewed, and `cancel-in-progress` makes that window real. - **With the job token only:** enable *Allow GitHub Actions to create and approve pull requests* in the repository's (or organisation's) Actions - settings, or APPROVE returns 422. A review the token cannot post is a - warning in the log, never a change to the check: the score decides the - exit status, so a clean PR stays green and simply gets no review. + settings, or APPROVE returns 422. A refused approval falls back to the + "would approve" COMMENT, which quotes the refusal and clears the run's own + earlier request-changes, so the PR is not left blocked by a round it has + since passed. Any other review the token cannot post is a warning in the + log, never a change to the check: the score decides the exit status, so a + clean PR stays green and simply gets no review. - **CODEOWNERS:** if *Require review from Code Owners* is on, the approval only satisfies it when the posting identity is a code owner. diff --git a/review/submit-verdict.mjs b/review/submit-verdict.mjs index a828025..af3d398 100644 --- a/review/submit-verdict.mjs +++ b/review/submit-verdict.mjs @@ -12,6 +12,7 @@ * clean, but a human must look COMMENT naming why, job passes * clean, approve-when-clean off COMMENT "would approve", job passes * clean, approve-when-clean on APPROVE, job passes + * clean, but GitHub refuses the APPROVE COMMENT saying so, job passes * no or unparseable output nothing submitted, job fails * * Nothing is submitted on a crashed reviewer on purpose: a changes-requested @@ -246,6 +247,17 @@ const dismissOwnStateReviews = async (own) => { } }; +const approveHint = (error) => { + if (!/HTTP 422/.test(error.message)) return; + console.log( + 'HTTP 422 on a review is usually one of two things: the job token is not allowed to ' + + 'approve — enable "Allow GitHub Actions to create and approve pull requests" in the ' + + 'repository or organisation Actions settings — or the owner of the github-token ' + + 'secret authored this PR, which GitHub refuses to let anyone approve or request ' + + 'changes on. Use a machine account or GitHub App rather than a person\'s token.' + ); +}; + const counts = ['BLOCKER', 'HIGH', 'MEDIUM', 'LOW'] .map((s) => [s, findings.filter((f) => f.severity === s).length]) .filter(([, n]) => n > 0) @@ -289,11 +301,30 @@ try { ]); await dismissOwnStateReviews(own); } else { - await submit('APPROVE', [ - `**Approved.** ${countLine}`, - '', - 'Nothing blocks and no human review is needed.', - ]); + // An APPROVE supersedes the identity's earlier REQUEST_CHANGES only if + // it lands. Refused (the Actions approve setting, a token whose owner + // authored the PR), fall back to the COMMENT path so the stale block + // is still cleared and the refusal is on the PR, not only in the log. + try { + await submit('APPROVE', [ + `**Approved.** ${countLine}`, + '', + 'Nothing blocks and no human review is needed.', + ]); + } catch (error) { + console.log(`::warning::could not approve: ${escapeData(error.message)}`); + approveHint(error); + const own = await submit('COMMENT', [ + `**Would approve.** ${countLine}`, + '', + 'Nothing blocks and no human review is needed, but GitHub refused the approval:', + '', + `> ${error.message.replace(/\s+/g, ' ').slice(0, 300)}`, + '', + 'With the job token this needs *Allow GitHub Actions to create and approve pull requests* in the repository\'s Actions settings; otherwise pass a `github-token` from a machine account.', + ]); + await dismissOwnStateReviews(own); + } } } } catch (error) { @@ -303,15 +334,7 @@ try { // red for good. console.log(`::warning::could not submit the review: ${escapeData(error.message)}`); console.log('The check still reports the score below; only the PR review is missing.'); - if (/HTTP 422/.test(error.message)) { - console.log( - 'HTTP 422 on a review is usually one of two things: the job token is not allowed to ' + - 'approve — enable "Allow GitHub Actions to create and approve pull requests" in the ' + - 'repository or organisation Actions settings — or the owner of the github-token ' + - 'secret authored this PR, which GitHub refuses to let anyone approve or request ' + - 'changes on. Use a machine account or GitHub App rather than a person\'s token.' - ); - } + approveHint(error); } if (blocking.length === 0) {