feat: skip re-posting when review is unchanged (preserve threads)
CI / test (pull_request) Successful in 14s
CI / review (gpt-4.1, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 23s
CI / review (gpt-5, security, SECURITY_REVIEW.md, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m1s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 1m28s

Before posting, compare the new review body+event against the existing
review with the same sentinel. If identical, skip entirely — this
preserves conversation threads on inline comments and avoids
re-notifying reviewers for findings they already know about.

Only re-posts when findings actually change (fixed, new, or different).

Tests: 6 cases covering identical, different body, different state,
stale reviews, and different sentinels.
This commit is contained in:
Rodin
2026-05-01 22:17:36 -07:00
parent b0dc6d0c09
commit 1c2292265b
2 changed files with 108 additions and 0 deletions
+29
View File
@@ -257,6 +257,15 @@ func main() {
log.Printf("Attaching %d inline comments", len(inlineComments))
}
// Check if existing review is unchanged — skip to preserve conversation threads
if *updateExisting && *reviewerName != "" {
existingReviews, err := giteaClient.ListReviews(ctx, owner, repoName, prNumber)
if err == nil && reviewUnchanged(existingReviews, reviewBody, event, sentinel) {
log.Printf("Review unchanged from previous run; skipping to preserve threads")
return
}
}
log.Printf("Posting review (event=%s)...", event)
posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, event, reviewBody, inlineComments)
if err != nil {
@@ -465,3 +474,23 @@ func shouldEscalate(reviews []gitea.Review, postedID int64, postedLogin, ownSent
}
return false
}
// reviewUnchanged checks if an existing review with the same sentinel
// already has identical body and state. Returns true if a re-post would
// produce the same result (skip to preserve conversation threads).
func reviewUnchanged(reviews []gitea.Review, newBody, newEvent, sentinel string) bool {
for _, r := range reviews {
if r.Stale {
continue
}
if !strings.Contains(r.Body, sentinel) {
continue
}
// Compare state (map APPROVED back from Gitea's representation)
existingEvent := r.State
if existingEvent == r.State && existingEvent == newEvent && r.Body == newBody {
return true
}
}
return false
}