From 51de368492805e3a28b7eefc30aee21632942522 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 17:11:29 +0000 Subject: [PATCH 1/2] ci: trigger a Cursor review on new commits, debounced by 5 minutes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Cursor automation webhook only fired at PR open (or ready-for-review, label, /cursor review command): commits pushed after the initial review — including autofix passes — were never re-reviewed. Add the synchronize trigger with a per-PR debounce: - All synchronize events of one PR share a concurrency group with cancel-in-progress, and the job sleeps 5 minutes before calling the webhook. A burst of pushes therefore yields ONE review, of its last commit — important because a Cursor run cannot be cancelled once fired. - After the sleep, the job re-checks the PR: if the head moved anyway (cancellation race) or the PR closed meanwhile, it skips instead of firing a stale or useless review. - Pushes to drafts are ignored (same as opened), and the existing AUTO_ALLOWED gate applies: no automatic review for dependency bots or authors without a merged contribution. Other triggers (open, label, command, manual) are unaffected: they get a unique concurrency group and are never debounced nor cancelled. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VU6y2pzeyAXZWPrS4YNGwx --- .../workflows/cursor-automation-webhook.yml | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cursor-automation-webhook.yml b/.github/workflows/cursor-automation-webhook.yml index 24ee2392b4..2c3fb51f9c 100644 --- a/.github/workflows/cursor-automation-webhook.yml +++ b/.github/workflows/cursor-automation-webhook.yml @@ -2,6 +2,8 @@ name: Trigger Cursor automation # Starts a Cursor Automation webhook for a given PR, either: # - automatically when a PR is opened (or marked "ready for review"), +# - automatically ~5 minutes after new commits are pushed to the PR +# (debounced: a burst of pushes yields ONE review, of its last commit), # - when anyone allowed to do so comments "/cursor review" on the PR, # - when the label "needs:cursor-review" is added to a PR, or # - manually via workflow_dispatch. @@ -22,7 +24,7 @@ name: Trigger Cursor automation on: # Secrets are available for fork PRs with pull_request_target. pull_request_target: - types: [opened, ready_for_review, labeled] + types: [opened, ready_for_review, labeled, synchronize] # Comment command. issue_comment always runs from the base branch with # secrets available, including for comments on fork PRs. issue_comment: @@ -58,6 +60,18 @@ jobs: github.event.issue.pull_request != null && contains(github.event.comment.body, '/cursor')) runs-on: ubuntu-latest + # Debounce for push bursts: every synchronize event of a given PR shares + # one concurrency group with cancel-in-progress, so a new push cancels + # the previous job while it sleeps in "Debounce push bursts" below and + # restarts the 5-minute timer — one review per burst, of its last + # commit, and no way to stack reviews (Cursor runs cannot be cancelled + # once fired). Every other trigger gets a unique group (run_id) and is + # never debounced nor cancelled. + concurrency: + group: ${{ github.event_name == 'pull_request_target' && github.event.action == 'synchronize' && format('cursor-review-sync-{0}', github.event.pull_request.number) || format('cursor-review-{0}', github.run_id) }} + cancel-in-progress: true + # Covers the 5-minute debounce sleep plus the API calls. + timeout-minutes: 15 steps: - name: Resolve trigger id: gate @@ -161,6 +175,17 @@ jobs: RUN=true fi ;; + synchronize) + # New commits pushed to the PR: same eligibility rules as + # "opened" (the AUTO_ALLOWED gate below), plus the debounce + # step before the webhook call. + if [ "$EVENT_PR_DRAFT" = 'true' ]; then + echo "PR #${PR_NUMBER}: push on a draft, the review will run when it is marked ready for review." + else + REASON='pr_synchronize' + RUN=true + fi + ;; esac # Applies to the automatic triggers only: the label and @@ -241,8 +266,42 @@ jobs: exit 1 fi + - name: Debounce push bursts + id: debounce + if: steps.gate.outputs.run == 'true' && steps.gate.outputs.reason == 'pr_synchronize' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ steps.gate.outputs.number }} + EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -uo pipefail + + # Wait out the burst: while this sleeps, any newer push cancels + # the whole job through the concurrency group and restarts its own + # 5-minute timer, so only the last push of a burst reaches the + # webhook call. + sleep 300 + + # Belt and braces for cancellation races: if the head moved anyway + # or the PR is no longer open, skip — the newer push's run (or + # nobody) fires the review. + PR_JSON=$(gh api "repos/${GH_REPO}/pulls/${PR_NUMBER}") \ + || { echo "::warning::Could not re-fetch PR #${PR_NUMBER} after the debounce, skipping."; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } + STATE=$(echo "$PR_JSON" | jq -r '.state') + HEAD_SHA=$(echo "$PR_JSON" | jq -r '.head.sha') + if [ "$STATE" != 'open' ]; then + echo "PR #${PR_NUMBER} is ${STATE} after the debounce: not firing a review." + echo "skip=true" >> "$GITHUB_OUTPUT" + elif [ "$HEAD_SHA" != "$EVENT_HEAD_SHA" ]; then + echo "PR #${PR_NUMBER} moved from ${EVENT_HEAD_SHA} to ${HEAD_SHA} during the debounce: the newer push's run fires the review." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - name: Fetch PR metadata and call Cursor webhook - if: steps.gate.outputs.run == 'true' + if: steps.gate.outputs.run == 'true' && steps.debounce.outputs.skip != 'true' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} From 014df5b1544a032918742016d8395df2a8cd991d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 17:19:33 +0000 Subject: [PATCH 2/2] docs: document the automatic post-push Cursor review in CONTRIBUTING Addresses the review feedback on the synchronize trigger: CONTRIBUTING.md still described the initial-review-only contract. It now explains the automatic re-review ~5 minutes after a push (and why the delay exists), repositions /cursor review and the needs:cursor-review label as the on-demand / first-timer / skip-the-wait hatches, and warns that commenting right after a push can start a second review of the same commit. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VU6y2pzeyAXZWPrS4YNGwx --- .github/CONTRIBUTING.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e6d4ea1e2b..ed8acff345 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -65,11 +65,11 @@ If your PR implements a forum request, add a line `Forum: https://community.glad ## 🤖 Automated review -An automated review runs on your PR as soon as you open it. A draft PR waits until you mark it **ready for review**. +An automated review runs on your PR as soon as you open it. A draft PR waits until you mark it **ready for review**. After that, every time you push new commits, a new review starts automatically about 5 minutes later — the delay lets a burst of pushes result in a single review of your latest commit, so there is no need to hold back small fixup commits. -Two cases do not get that automatic review: PRs authored by `dependabot[bot]` or `renovate[bot]`, and PRs from contributors whose first contribution has not been merged yet. **If this is your first PR here, just ask for a review in a comment** (see below) or wait for a maintainer — nothing is wrong with your PR. +Two cases do not get those automatic reviews: PRs authored by `dependabot[bot]` or `renovate[bot]`, and PRs from contributors whose first contribution has not been merged yet. **If this is your first PR here, just ask for a review in a comment** (see below) or wait for a maintainer — nothing is wrong with your PR. -To ask for a new review after pushing changes, comment on the PR: +To ask for a review on demand (as a first-time contributor, or to skip the 5-minute wait), comment on the PR: ```text /cursor review @@ -79,6 +79,8 @@ To ask for a new review after pushing changes, comment on the PR: The command works for the PR author and for repository owners, organization members and collaborators. The bot reacts to your comment with 👀 when the request is accepted, 🚀 once the review has been started, and 👎 if you are not allowed to ask for one. Maintainers can also add the `needs:cursor-review` label, which triggers the same thing. +Note that the command fires immediately and is independent of the automatic post-push review: commenting `/cursor review` right after pushing can start two reviews of the same commit (the on-demand one now, the automatic one ~5 minutes later). After a push, the cheapest option is simply to wait. + --- ## 📜 Licensing