Hmbown/CodeWhale · error

Complete PR diff fingerprint changed before review…

Error message

Complete PR diff fingerprint changed before review aggregation

What it means

`finish` verifies that the complete PR diff it is given still matches the `diff_fingerprint` captured in the manifest when the review passes were planned. If the fingerprint differs, the underlying PR changed mid-review, so per-pass outputs may reference stale diffs and the aggregated review would be unsound.

Solutions

  1. Re-run the review against the current PR state so the manifest fingerprint and diff match
  2. Avoid pushing to the PR while a review is in flight; finish or cancel the review first
  3. Pin the diff content used for planning and aggregation rather than re-fetching

Example fix

// before
let diff = fetch_fresh_diff(pr)?;
aggregator.finish(&diff)?;
// after
let diff = fetch_fresh_diff(pr)?;
if diff_fingerprint(&diff) != manifest.diff_fingerprint {
    return Err(anyhow!("PR changed during review; restart the review"));
}
aggregator.finish(&diff)?;
Defensive patterns

Strategy: validation

Validate before calling

if diff_fingerprint(&complete_diff) != manifest.diff_fingerprint { return Err(anyhow!("PR diff changed mid-review; restart")); }

Try / catch

match aggregator.finish(&diff) { Err(e) if e.to_string().contains("fingerprint changed") => restart_review_with_current_diff(), Err(e) => return Err(e), Ok(v) => v }

Prevention

When it happens

Trigger: Calling `finish` with a `complete_diff` whose `diff_fingerprint` differs from `self.manifest.diff_fingerprint` — typically because the PR gained new commits, or the diff was re-fetched from a different base/head, between planning the passes and aggregating.

Common situations: A developer pushes commits to the PR while a long review runs; a rebase or force-push changes the diff; the review tool re-fetched the diff after the manifest was built; flaky diff ordering from `gh pr diff`.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/90be5d0132cd1d15. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/review.rs:730

                self.manifest.passes.len()
            )
        })?;
        self.outputs.push(output);
        self.raw_outputs.push(raw);
        Ok(())
    }

    pub(crate) fn finish(
        self,
        complete_diff: &str,
    ) -> anyhow::Result<(ReviewOutput, String, ReviewReceiptCoverage)> {
        anyhow::ensure!(
            self.outputs.len() == self.manifest.passes.len(),
            "Only {}/{} review passes completed; the partial review was not accepted or posted.",
            self.outputs.len(),
            self.manifest.passes.len()
        );
        anyhow::ensure!(
            diff_fingerprint(complete_diff) == self.manifest.diff_fingerprint,
            "Complete PR diff fingerprint changed before review aggregation"
        );
        let mut issues = Vec::new();
        let mut suggestions = Vec::new();
        let mut summaries = Vec::new();
        let mut assessments = Vec::new();
        for (index, output) in self.outputs.into_iter().enumerate() {
            if !output.summary.is_empty() {
                summaries.push(format!("Pass {}: {}", index + 1, output.summary));
            }
            if !output.overall_assessment.is_empty() {
                assessments.push(format!("Pass {}: {}", index + 1, output.overall_assessment));
            }
            issues.extend(output.issues);
            suggestions.extend(output.suggestions);
        }
        let total = self.manifest.passes.len();

View on GitHub (pinned to 73e0f67d83)