Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions tekton/v1/tasks/ci/coderabbit-review-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# CodeRabbit review label

The `coderabbit-review-label` Task reconciles `do-not-merge/request-change`.
It supplements the existing OWNERS, approved and lgtm requirements; it does not
require every commit to receive a CodeRabbit approval.

## Policy

- Reconcile only on CodeRabbit `pull_request_review` events with action
`submitted` or `dismissed`. Opening, reopening or pushing to a PR does not
trigger this Task; existing labels persist until a later review is processed.
- Read PR state and reviews with `gh pr view --json headRefOid,state,labels,reviews`;
gh automatically paginates reviews. Select GraphQL author login `coderabbitai`.
The webhook filter still uses REST login `coderabbitai[bot]` and type `Bot`;
`gh pr view` does not expose the review author's type.
- Order submitted decisions by submission time, using API array order to break
ties (GraphQL review IDs are opaque), ignoring COMMENTED
and PENDING reviews. Include DISMISSED as a conservative barrier: dismissal
must not revive an older approval.
- The latest CHANGES_REQUESTED adds the blocker, including when the PR has since
received new commits.
- The latest APPROVED can remove an existing blocker only when its `commit_id`
equals the current PR head SHA. Recheck the snapshot before and after removal.
- All other states preserve the existing label. Resolving a conversation alone
does not clear it. An approval followed by a push does not create a new blocker.
- The label stores the outstanding block. There is no historical-approval search
that can resurrect a resolved objection. Manual bypass is not implemented.

Label changes use the same `gh pr edit --add-label/--remove-label` interface as
the other label Tasks in this repository. API errors fail the TaskRun. If
verification fails after deletion, the Task attempts to restore the label;
failed runs need operator attention and retry.
No PR code is checked out or executed. Event values enter the shell through
environment variables, and repository/PR identifiers are validated.

## Rollout (deployment and event routing pending)

1. The pilot is `ti-community-infra/configs`, restricted to PRs whose base
branch is exactly `test_ai_review`, in
`tekton/v1/triggers/triggers/env-gcp/_/github-pr-coderabbit-review-label.yaml`.
Create the branch separately if it does not exist. CodeRabbit must review PRs
targeting this non-default branch; creating or retargeting a PR alone is not
sufficient evidence that the review event chain works.
2. Deploy the registered Task, TriggerTemplate and Trigger to the same namespace.
The existing `github` secret needs PR read and issue label write permissions.
The selected release image must provide bash, gh (with `gh pr view --json`
support for the fields above and automatic review pagination), and jq.
3. Verify the authenticated webhook/EventListener route and its trigger selector.
This Trigger uses the existing `type: github-pr` label but also needs delivery
of `pull_request_review` events. EventListener definitions are not in this
change; do not assume the pull_request selector accepts review events.
4. In configs/prow/config/plugins.yaml, add `pull_request_review` to exactly the
external-plugin endpoint serving this deployment for the pilot repository
`ti-community-infra/configs`. This Task only requires `pull_request_review`;
preserve existing plugin entries and event subscriptions for other tasks.
Do not enable both tekton2-ee-cd and prow-tekton without verifying routing.
5. Sync the new label definition from configs. Every Tide query admitting a pilot
PR must exclude the exact label; bare `do-not-merge` is not a wildcard.
6. Reconcile existing open pilot PRs using the Task before relying on the gate.
Historical dismissed reviews cannot reconstruct a missing blocker; inspect
these PRs explicitly during rollout.

## Limits

This is an asynchronous Tide label gate, not a GitHub required check or a lock
on manual merging. Snapshot rechecks and restoration reduce stale writes but do
not serialize concurrent TaskRuns or atomically coordinate with Tide. A failed
delivery, process termination, or concurrent merge can leave a window before
the label is applied/restored. This implementation must not be described as a
zero-race merge guarantee. Retries re-read GitHub state and are safe to repeat.

No automatic backfill, periodic repair, protected manual override, or additional
label permission policy is installed by this change. These are rollout or future
extensions, not existing functionality.
130 changes: 130 additions & 0 deletions tekton/v1/tasks/ci/coderabbit-review-label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: coderabbit-review-label
spec:
description: Block on CodeRabbit objections; only a current-head approval clears the label.
params:
- name: owner
type: string
- name: repo
type: string
- name: number
type: string
workspaces:
- name: github
description: GitHub token with pull request read and issue label write permissions.
steps:
- name: reconcile
image: ghcr.io/pingcap-qe/cd/utils/release:v2026.7.12-4-g372cd24
env:
- name: PR_OWNER
value: $(params.owner)
- name: PR_REPO
value: $(params.repo)
- name: PR_NUMBER
value: $(params.number)
- name: TOKEN_FILE
value: $(workspaces.github.path)/token
script: |
#!/usr/bin/env bash
set -euo pipefail
export GH_TOKEN="$(cat "$TOKEN_FILE")"
[[ "$PR_OWNER" =~ ^[A-Za-z0-9-]+$ ]]
[[ "$PR_REPO" =~ ^[A-Za-z0-9_.-]+$ ]]
[[ "$PR_NUMBER" =~ ^[0-9]+$ ]]
pr_url="https://github.com/$PR_OWNER/$PR_REPO/pull/$PR_NUMBER"
label='do-not-merge/request-change'
restore=false

add_label() {
gh pr edit --add-label "$label" "$pr_url" >/dev/null
}

# A failed read after deletion must not leave an unverified PR unblocked.
cleanup() {
result=$?
if [ "$restore" = true ]; then
add_label || result=1
fi
exit "$result"
}
trap cleanup EXIT

snapshot() {
local pr
pr=$(gh pr view "$PR_NUMBER" --repo "$PR_OWNER/$PR_REPO" \
--json headRefOid,state,labels,reviews) || return
# GraphQL uses coderabbitai, without the REST/webhook [bot] suffix.
# Review IDs are opaque; use API order to break submission-time ties.
jq -c --arg label "$label" '
(.reviews | to_entries | map(select(
.value.author.login == "coderabbitai" and
(.value.state == "APPROVED" or .value.state == "CHANGES_REQUESTED" or
.value.state == "DISMISSED")
)) | sort_by(.value.submittedAt, .key) | last | .value) as $review |
{head: .headRefOid, open: (.state == "OPEN"),
blocked: any(.labels[]; .name == $label),
review: ($review | if . == null then null else
{id, state, commit_id: .commit.oid, submitted_at: .submittedAt} end)}' <<< "$pr"
}

can_clear() {
jq -e '.open and .review.state == "APPROVED" and
.review.commit_id == .head' >/dev/null <<< "$1"
}

# Webhooks are hints. gh pr view paginates reviews to read current state.
# COMMENTED does not replace a decision. DISMISSED never revives an old
# approval. A new head alone neither creates nor clears a blocker.
for attempt in 1 2 3; do
current=$(snapshot)
if ! jq -e '.open' >/dev/null <<< "$current"; then
exit 0
fi
state=$(jq -r '.review.state // "NONE"' <<< "$current")
if [ "$state" = CHANGES_REQUESTED ]; then
add_label
after=$(snapshot)
if [ "$(jq -c '{head, review}' <<< "$current")" != \
"$(jq -c '{head, review}' <<< "$after")" ]; then
continue
fi
echo 'CodeRabbit requested changes; blocking Tide.'
exit 0
fi
if ! can_clear "$current"; then
echo 'No current-head approval; preserving the existing label state.'
exit 0
fi
if ! jq -e '.blocked' >/dev/null <<< "$current"; then
exit 0
fi
before=$(snapshot)
if [ "$current" != "$before" ]; then
continue
fi
restore=true
delete_failed=false
if ! gh pr edit --remove-label "$label" "$pr_url" >/dev/null; then
# A concurrent TaskRun may already have removed the label. Verify
# the resulting state below instead of trusting this command alone.
delete_failed=true
fi
after=$(snapshot)
if can_clear "$after" && jq -e '.blocked | not' >/dev/null <<< "$after" && \
[ "$(jq -c '{head, review}' <<< "$before")" = \
"$(jq -c '{head, review}' <<< "$after")" ]; then
restore=false
echo 'Current-head CodeRabbit approval cleared the blocker.'
exit 0
fi
add_label
restore=false
if [ "$delete_failed" = true ]; then
echo 'Failed to remove the blocker label; retained it.' >&2
exit 1
fi
done
echo 'PR changed during reconciliation; retained the blocker. Retry this TaskRun.' >&2
exit 1
1 change: 1 addition & 0 deletions tekton/v1/tasks/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ci/ci-helper-for-pr.yaml
- ci/coderabbit-review-label.yaml
- ci/resolve-cherrypick-conflicts.yaml
- delivery/pingcap-deliver-binaries.yaml
- delivery/pingcap-deliver-images.yaml
Expand Down
28 changes: 28 additions & 0 deletions tekton/v1/triggers/templates/_/coderabbit-review-label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: coderabbit-review-label
spec:
params:
- name: pr-owner
- name: pr-repo
- name: pr-number
resourcetemplates:
- apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: coderabbit-review-label-
spec:
params:
- name: owner
value: $(tt.params.pr-owner)
- name: repo
value: $(tt.params.pr-repo)
- name: number
value: $(tt.params.pr-number)
taskRef:
name: coderabbit-review-label
workspaces:
- name: github
secret:
secretName: github
1 change: 1 addition & 0 deletions tekton/v1/triggers/templates/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ resources:
- _/build-component-linux.yaml
- _/build-component-single-platform.yaml
- _/ci-helper-for-pr.yaml
- _/coderabbit-review-label.yaml
- _/collect-multi-arch-image.yaml
# - _/push-oci-artifact-to-fileserver.yaml
- pingcap/bump-placeholder-version-in-readme.yaml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: triggers.tekton.dev/v1beta1
kind: Trigger
metadata:
name: github-pr-coderabbit-review-label
labels:
type: github-pr
spec:
interceptors:
- name: filter rollout repositories and review events
ref: { name: cel }
params:
- name: filter
# Pilot scope: only PRs targeting this repository and base branch.
value: >-
body.repository.full_name in ['ti-community-infra/configs']
&& body.pull_request.base.ref == 'test_ai_review'
&& header.match('X-GitHub-Event', 'pull_request_review')
&& body.action in ['submitted', 'dismissed']
&& body.review.user.login == 'coderabbitai[bot]'
&& body.review.user.type == 'Bot'
bindings:
- ref: github-pr
template:
ref: coderabbit-review-label
1 change: 1 addition & 0 deletions tekton/v1/triggers/triggers/env-gcp/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ resources:
- _/git-push-branch-build-failpoint.yaml
- _/git-push-branch-build-ng.yaml
- _/git-push-on-fips-branches.yaml
- _/github-pr-coderabbit-review-label.yaml
- _/github-pr-labeled-with-lgtm.yaml
- _/github-pr-labeled-with-needs-ok-to-test.yaml
- _/github-pr-synchronized-remove-hold.yaml
Expand Down