From aad42d4d4696356ff77c9ea88f0324ad11ebad5b Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:56:35 +0300 Subject: [PATCH 1/2] qodo-gate: re-evaluate when Qodo posts its review comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #2269, from Qodo's own review of it. That PR taught the gate to accept an issue comment headed "Code Review by Qodo" as proof of review, which is the only proof a zero-finding PR ever produces. It did not give the workflow a reason to look again when that comment arrives. So the gap only moved. On a clean PR the run fired at `opened` fails — Qodo has not answered yet — the comment lands a minute or two later, and nothing re-triggers the check. It stays red until an unrelated push. That is exactly how #2269 itself went green: a follow-up commit, not the review. The PR that introduced the detection fix hid its own trigger gap. Adds the issue_comment trigger, plus the plumbing it needs: - a job guard, since issue_comment also fires for plain issues, which have no PR to gate. Every other trigger here is PR-only. - the PR number from github.event.issue.number, as issue_comment carries `issue` rather than `pull_request`; for a PR comment the issue number is the PR number. - a head sha lookup in the sweep step, which reads it from the pull_request payload that issue_comment does not have. Resolved from the PR instead, and still best-effort: a failed lookup skips the sweep rather than turning a PASS into a FAIL. The sweep matters most on precisely this trigger — it is the one that clears a clean PR's earlier red run off the commit. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/qodo-gate.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qodo-gate.yml b/.github/workflows/qodo-gate.yml index 8af61d4583..cf8cceb687 100644 --- a/.github/workflows/qodo-gate.yml +++ b/.github/workflows/qodo-gate.yml @@ -45,6 +45,14 @@ on: types: [submitted] pull_request_review_comment: types: [created, deleted] + # A clean PR's only proof of review is an ISSUE comment, and without this + # trigger nothing re-evaluates the gate when it arrives: the run fired at + # `opened` fails (Qodo has not answered yet), the comment lands a minute or + # two later, and the check stays red until an unrelated push happens to + # re-trigger it. That is precisely how #2269 went green — a follow-up commit, + # not the review — which hid the gap on the PR that introduced it. + issue_comment: + types: [created, edited] permissions: contents: read @@ -54,11 +62,16 @@ permissions: jobs: qodo-gate: runs-on: ubuntu-latest + # issue_comment fires for plain issues too, which have no PR to gate. + # Every other trigger here is PR-only, so this is the whole guard. + if: github.event_name != 'issue_comment' || github.event.issue.pull_request steps: - name: Require a Qodo review with all its threads resolved env: GH_TOKEN: ${{ github.token }} - PR: ${{ github.event.pull_request.number }} + # issue_comment carries `issue`, not `pull_request` — for a PR + # comment the issue number IS the PR number. + PR: ${{ github.event.pull_request.number || github.event.issue.number }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} run: | @@ -185,10 +198,21 @@ jobs: env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} + # Empty on issue_comment, which carries no head sha — resolved from + # the PR below. This is the trigger that matters most for the sweep: + # it is the one that turns a clean PR's earlier red run green. HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR: ${{ github.event.pull_request.number || github.event.issue.number }} THIS_RUN: ${{ github.run_id }} run: | set -u + if [ -z "$HEAD_SHA" ]; then + if ! HEAD_SHA=$(gh pr view "$PR" --repo "$REPO" --json headRefOid \ + --jq .headRefOid); then + echo "sweep skipped: could not resolve head sha for PR $PR" + exit 0 + fi + fi # The listing is guarded too, not just the reruns: with an unguarded # pipeline a transient list failure would be the step's exit code — # exactly the PASS-into-FAIL this step promises not to produce. From 6b03c772c7c0b15982510944824a39c2a01d3269 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:10:17 +0300 Subject: [PATCH 2/2] qodo-gate: only let Qodo's own review comment trigger the gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second finding from Qodo on this PR, and a fair one: the issue_comment trigger as first written fired for ANY comment on ANY pull request. The guard only established that the thing commented on was a PR. That makes a required check into an API-heavy workflow, holding `actions: write` for the rerun sweep, that any permitted commenter could run as often as they liked — CI noise, and needless rate-limit exposure on a check that blocks merges. The guard now also requires the comment to be the bot's, and to carry the "Code Review by Qodo" header. Both matter: - author: only Qodo's own comment is evidence Qodo reviewed. A human typing /review still works — Qodo answers, and its answer triggers this. - body: the bot also posts "Qodo is busy working", written before it has read anything, and "PR Summary by Qodo", which is /describe output. Neither means the diff was reviewed. Matching the review header also keeps this to one run per review instead of three. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/qodo-gate.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/qodo-gate.yml b/.github/workflows/qodo-gate.yml index cf8cceb687..28814de22d 100644 --- a/.github/workflows/qodo-gate.yml +++ b/.github/workflows/qodo-gate.yml @@ -62,9 +62,28 @@ permissions: jobs: qodo-gate: runs-on: ubuntu-latest - # issue_comment fires for plain issues too, which have no PR to gate. - # Every other trigger here is PR-only, so this is the whole guard. - if: github.event_name != 'issue_comment' || github.event.issue.pull_request + # Narrow the issue_comment trigger to the single comment that can change + # this gate's verdict: Qodo's review. Without all three clauses the job is + # an API-heavy workflow holding `actions: write` that any commenter could + # run at will, on any PR, as often as they liked — CI noise, and needless + # rate-limit exposure on a REQUIRED check. + # + # issue.pull_request — issue_comment fires for plain issues too, and + # those have no PR to gate. + # comment author — only the bot's own comment is evidence of review. + # A human typing /review still works: Qodo answers, + # and that answer is what triggers this. + # comment body — the bot also posts "Qodo is busy working" and + # "PR Summary by Qodo", neither of which means the + # diff was reviewed. Matching the review header + # keeps this to one run per review. + # + # Every other trigger here is PR-only and needs no guard. + if: >- + github.event_name != 'issue_comment' || + (github.event.issue.pull_request && + startsWith(github.event.comment.user.login, 'qodo-free-for-open-source-projects') && + contains(github.event.comment.body, 'Code Review by Qodo')) steps: - name: Require a Qodo review with all its threads resolved env: