diff --git a/.github/workflows/qodo-gate.yml b/.github/workflows/qodo-gate.yml index aa9f05f..2085a35 100644 --- a/.github/workflows/qodo-gate.yml +++ b/.github/workflows/qodo-gate.yml @@ -20,11 +20,20 @@ # Event-driven: re-evaluates when the PR updates, when a review is submitted, # and when someone replies in a review thread. GitHub's workflow parser # rejects the documented `pull_request_review_thread` trigger ("Unexpected -# value"), so plain thread resolution does not auto-retrigger — after -# resolving the last thread, either leave a reply (retriggers) or Re-run the -# failed qodo-gate check from the PR's Checks tab; the check reads the live -# resolution state each run. Escape hatch for a Qodo outage: the -# `skip-qodo-gate` label passes the check (label changes re-trigger it). +# value" — re-verified empirically, zero-job "workflow file issue" run), so +# plain thread resolution does not auto-retrigger — after resolving the last +# thread, leave a reply (retriggers); the check reads the live resolution +# state each run. Escape hatch for a Qodo outage: the `skip-qodo-gate` label +# passes the check (label changes re-trigger it). +# +# A passing run also re-runs this workflow's earlier FAILED runs on the same +# head commit. Each trigger event creates its own workflow run, and branch +# protection's rollup counts every run of a required check on the commit — a +# fresh green run sits beside the stale red ones rather than superseding +# them, so the PR stays BLOCKED until each red run is re-run by hand (four +# clicks on PR #396). Only a passing run re-runs others and a re-run that +# passes finds nothing red left, so it converges; if threads are genuinely +# unresolved the re-runs go red again and the gate still holds. name: qodo-gate on: pull_request: @@ -37,6 +46,7 @@ on: permissions: contents: read pull-requests: read + actions: write jobs: qodo-gate: @@ -133,9 +143,36 @@ jobs: echo "FAIL: $unresolved unresolved Qodo review thread(s) — address" echo "or explicitly dismiss each finding in its thread, then mark" echo "it resolved. Plain resolution does not auto-retrigger this" - echo "check: leave a reply in a thread (retriggers) or Re-run the" - echo "check from the PR's Checks tab after resolving." + echo "check: leave a reply in a thread (retriggers) — the passing" + echo "run then sweeps this red run off the commit itself." exit 1 fi echo "PASS: Qodo review present, all its threads resolved" + + # Sweep stale red runs of this gate off the head commit, so the pass + # above is the one the branch-protection rollup sees. Best-effort: a + # failed re-run request must not turn a PASS into a FAIL. + - name: Re-run this gate's earlier failed runs on this commit + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + THIS_RUN: ${{ github.run_id }} + run: | + set -u + # 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. + if ! ids=$(gh run list --repo "$REPO" --workflow qodo-gate \ + --commit "$HEAD_SHA" --json databaseId,conclusion \ + --jq '.[] | select(.conclusion == "failure") | .databaseId'); then + echo "sweep skipped: could not list this workflow's runs" + exit 0 + fi + for id in $ids; do + [ "$id" = "$THIS_RUN" ] && continue + echo "re-running failed qodo-gate run $id" + gh run rerun "$id" --repo "$REPO" --failed || true + done + exit 0