## TL;DR
Review a frozen snapshot, not the live PR: snapshot the head SHA at run start, require a quiet period with no pushes before starting, and cap re-reviews per PR. A review that chases every push will never finish on an active PR.

## The problem

```text
review agent failed to terminate its review loop: the PR kept updating: how to fix
```

## When to use
- The agent restarts its review on every push and never posts
- Review jobs pile up behind a single active PR
- Push-triggered runs overlap each other
- The queue backs up during busy development hours

## When not to use
- The agent loops on a static diff with no new pushes (a different loop bug)
- Reviews finish but are slow (a performance issue, not a moving target)
- You want a review of every single push by policy (then budget for it explicitly)

## Steps

### 1. Snapshot the head SHA when the run starts
Record the head SHA as the review target and review exactly that snapshot. Ignore pushes that land mid-run except to note them.

```bash
gh pr view 123 --json headRefOid --jq .headRefOid | tee /tmp/review-target-sha.txt
```

Expected output: a fixed target SHA; the review's scope is frozen regardless of what lands afterward.

### 2. Add a quiet-period gate before starting
Do not start the review until no push has arrived for N minutes (5 is a good default). Poll the head SHA or use push webhooks to detect activity.

Expected output: reviews start on settled heads instead of mid-flurry, cutting re-review triggers sharply.

### 3. Cap iterations per PR
Allow at most one re-review per PR per hour, or one per new head SHA, whichever is smaller. Extra triggers within the window are coalesced, not run.

Expected output: a hard ceiling on compute per PR; the loop cannot run away no matter how fast pushes arrive.

### 4. On a mid-run push, finish the pass and label it
Complete the current pass against the snapshotted SHA, label the review with the SHA it covered, and schedule at most one follow-up for the new head.

Expected output: every posted review states exactly which code it covered; follow-ups are bounded.

### 5. Debounce the trigger itself
Coalesce push events within the quiet window into a single run instead of queueing one run per push. Expected output: three pushes in five minutes produce one review, not three.

## Tool compatibility
- GitHub push webhooks and the pull_request synchronize event: the standard triggers to debounce.
- gh CLI 2.x: `pr view --json headRefOid` for quiet-period polling.
- Any scheduler or queue: the snapshot, quiet-period, and cap pattern ports across CI systems.

## Variant phrasings

### review agent keeps restarting on every push
Same fix. The debounce (step 5) plus the quiet period (step 2) together eliminate the restart storm.

### infinite re-review loop on active PR
Check the cap (step 3) first. Without a cap, any trigger bug becomes an infinite loop; the cap turns it into a bounded annoyance.

### PR updates faster than the review finishes
Short of freezing the PR, the snapshot approach (step 1) is the answer: review what existed when you started, and say so.

## Why it happens
Push-triggered automation assumes pushes are occasional, so each push gets a fresh full review. On an active PR, pushes arrive faster than reviews complete, and each new run invalidates the last. The system is stable only when the review rate exceeds the push rate - which stops being true exactly when the PR is hottest.

## Edge cases
- Merge-queue churn: the queue itself pushes; exempt queue-driven updates from the re-review trigger or the loop never ends.
- Stacked PRs where the base moves: treat a base change like a push for quiet-period purposes.
- Urgent hotfix PRs: allow an operator override to skip the quiet period, logged explicitly.
- Very long reviews: extend the quiet period beyond the typical review duration or snapshots go stale before posting.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_aYZ-TtECa9MugrZskWEWsA
