Repository navigation
91 lines (87 loc) · 5.22 KB
/
Copy pathcoderabbit-threads.yml
File metadata and controls
91 lines (87 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# CodeRabbit threads: every review thread CodeRabbit opens gets an answer
# before a gate ask (Sam, 2026-10-24, #2605). An answer is a reply inside the
# thread from anyone but CodeRabbit, the only form CodeRabbit reads. A PR
# comment never counts, not even one quoting the thread's link (D199,
# 2026-10-30): seats had turned those into letters CodeRabbit never sees. A
# review body never counts either. Resolved and outdated threads don't count.
# Fork PRs are skipped entirely.
#
# Reported as the commit status `coderabbit-threads` on the PR head, not as
# this workflow's own check, so the result always lands on the head commit.
# NOT a required check. It never checks out PR code, so the write token only
# reaches the statuses API.
name: CodeRabbit threads
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
pull_request_review:
# `deleted` too: removing the only reply must re-fail the status rather
# than leave a stale success on the head.
pull_request_review_comment:
types: [created, edited, deleted]
permissions:
contents: read
pull-requests: read
statuses: write
# No `concurrency:` on purpose (TASK-472). Runs started by a push, review or
# review comment attach their `count` check to the PR head, and a run that a
# concurrency group cancels, or a pending one it replaces, stays on the head as
# a CANCELLED check that reads as red after the threads are answered. Each run
# reads the threads fresh and finishes in seconds, so order is settled at post
# time instead: a run skips posting when the head's status already came from a
# newer run (a higher run id started later and read newer thread state).
jobs:
count:
runs-on: ubuntu-latest
timeout-minutes: 3
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
steps:
- name: Count unanswered CodeRabbit threads
run: |
set -euo pipefail
owner="${REPO%/*}"; name="${REPO#*/}"
gh api "repos/$REPO/pulls/$PR" > pr.json
# Fork PRs get no action at all (don't rely on a token error).
if [ "$(jq -r .head.repo.full_name pr.json)" != "$REPO" ]; then
echo "Fork PR: not counted, no status posted."; exit 0
fi
head=$(jq -r .head.sha pr.json)
# Every thread page; replies are read 100 deep per thread, and a
# thread with more than that and no visible reply is counted as
# INCOMPLETE rather than answered or unanswered.
gh api graphql --paginate --slurp -F owner="$owner" -F name="$name" -F pr="$PR" -f query='
query($owner:String!,$name:String!,$pr:Int!,$endCursor:String){repository(owner:$owner,name:$name){pullRequest(number:$pr){
reviewThreads(first:100,after:$endCursor){pageInfo{hasNextPage endCursor} nodes{isResolved isOutdated
comments(first:100){pageInfo{hasNextPage} nodes{author{login} databaseId url}}}}}}}' \
| jq '[.[].data.repository.pullRequest.reviewThreads.nodes[]]' > threads.json
# An answer is an in-thread reply from anyone but CodeRabbit. PR
# comments and review bodies are not read at all. A deleted account
# comes back as `author: null`; `// ""` keeps jq from exiting before
# the status posts, and counts that reply as an answer.
jq -r '.[]
| select(.isResolved|not) | select(.isOutdated|not)
| select((.comments.nodes[0].author.login // "") | test("^coderabbitai"))
| select([.comments.nodes[1:][] | select((.author.login // "") | test("^coderabbitai")|not)] | length == 0)
| "\(.comments.nodes[0].databaseId) \(.comments.pageInfo.hasNextPage) \(.comments.nodes[0].url)"' threads.json > open.txt
: > unanswered.txt; : > incomplete.txt
while read -r _id more url; do
if [ "$more" = true ]; then echo "$url" >> incomplete.txt; else echo "$url" >> unanswered.txt; fi
done < open.txt
n=$(wc -l < unanswered.txt | tr -d ' '); m=$(wc -l < incomplete.txt | tr -d ' ')
if [ "$n" != 0 ]; then state=failure; desc="$n unanswered CodeRabbit thread(s): reply inside each thread"
elif [ "$m" != 0 ]; then state=failure; desc="$m CodeRabbit thread(s) too long to read fully: check them by hand"
else state=success; desc="All CodeRabbit threads answered"; fi
echo "### $desc" >> "$GITHUB_STEP_SUMMARY"
sed 's/^/- /' unanswered.txt incomplete.txt >> "$GITHUB_STEP_SUMMARY"
cat unanswered.txt incomplete.txt
# Newest run wins: the status's target_url carries the run id that posted it.
posted=$(gh api "repos/$REPO/commits/$head/status" \
-q '[.statuses[] | select(.context == "coderabbit-threads" and .target_url != null) | .target_url | capture("/runs/(?<id>[0-9]+)").id | tonumber] | max // 0')
if [ "$posted" -gt "$GITHUB_RUN_ID" ]; then
echo "Run $posted already posted a newer result on $head; not overwriting it."; exit 0
fi
gh api -X POST "repos/$REPO/statuses/$head" -f state="$state" -f context=coderabbit-threads \
-f description="${desc:0:140}" -f target_url="$GITHUB_SERVER_URL/$REPO/actions/runs/$GITHUB_RUN_ID" > /dev/null