## TL;DR
Snapshot the head SHA when the review starts and re-check it right before posting. If a force push moved the head, recompute the diff and remap every line number to the new head - or abort the post and re-run. Posting positions computed against the old commit is what puts comments on the wrong lines.

## The problem

```text
review agent failed to refresh line numbers after a force push: stale commit references: how to fix
```

## When to use
- Inline comments appear on lines that do not match the code they quote
- GitHub instantly marks the agent's new comments as outdated
- A force push landed while the review was running
- The review API rejected comments with outdated diff hunk errors

## When not to use
- The agent never computed diff positions (an off-by-one or position-mapping bug, not a staleness bug)
- Comments are on the right lines but the wrong files (a file-matching issue)
- The PR was not force-pushed and the head SHA never changed

## Steps

### 1. Snapshot the head SHA at review start
```bash
gh pr view 123 --json headRefOid --jq .headRefOid | tee /tmp/review-head-sha.txt
```

Expected output: a 40-char SHA stored in the run metadata. Every position the agent computes is valid only against this SHA.

### 2. Re-check the head SHA immediately before posting
Read the current head SHA again and compare it to the snapshot. If it matches, post normally.

Expected output: a match (safe to post) or a mismatch, meaning a push landed mid-run and every computed position is suspect.

### 3. On mismatch, recompute the diff against the new head
```bash
git fetch origin pull/123/head
git diff OLD_SHA...NEW_SHA -- path/to/file.py
```

Expected output: the exact hunks the push changed, showing which findings still map and which need new positions.

### 4. Remap each finding to the new head or drop it
For findings in hunks the push did not touch, translate line numbers through the hunk offsets and post against the new commit id. For findings inside rewritten hunks, re-derive the position from the new diff; if it cannot be mapped, drop the finding and note it in the summary.

```json
{
  "body": "nit: rename for clarity",
  "commit_id": "NEW_HEAD_SHA",
  "path": "src/app.py",
  "line": 42,
  "side": "RIGHT"
}
```

Expected output: every posted comment carries the new head SHA with verified positions; unmappable findings are withheld instead of misplaced.

### 5. If the push rewrote most of the diff, re-run instead of remapping
When more than a handful of findings fail to map, abort and re-run the review against the new head. Expected output: a clean review on current code instead of remapped guesses.

## Tool compatibility
- GitHub REST review comments API: `commit_id`, `path`, `line`, and `side` must all describe the same head; works on github.com and GHES.
- gh CLI 2.x: `pr view --json headRefOid` for SHA snapshots.
- Review agents that post inline comments: keep the snapshot-and-recheck pattern regardless of which client library posts.

## Variant phrasings

### review comments marked outdated immediately after posting
Almost always a stale head SHA. Add the step-2 recheck; if pushes are frequent, shorten the gap between position computation and posting.

### force push invalidated the review positions
Same fix. For repos with heavy force-push traffic, consider the quiet-period gate from the moving-target guide so reviews start on settled heads.

### inline comment quoted the wrong line after a push
When the comment text is right but the anchor line is wrong, the mapping step (step 4) was skipped. Remap through the new hunks instead of reusing old line numbers.

## Why it happens
Inline comment positions are only meaningful relative to a specific commit. A force push replaces the analyzed commit with a new one, so earlier line numbers point at different code - or at lines that no longer exist. The mapping was done correctly, just against a commit that is no longer the head.

## Edge cases
- Rapid successive pushes: re-check in a loop until the SHA is stable, or give up after three attempts.
- Comments posted before the push were correct when posted; leave them.
- Renamed files in the push: path-based remapping fails; match by content similarity or drop the finding.
- Multi-commit PRs without force push: new commits appended normally do not invalidate old positions, only rebases and force pushes do.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_hea_2ewwfYOuYg0CKq5MfA
