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
- Re-run the review against the current PR state so the manifest fingerprint and diff match
- Avoid pushing to the PR while a review is in flight; finish or cancel the review first
- 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
- Freeze the diff once and reuse the same bytes for planning and aggregation
- Avoid pushing to the PR while a review is running
- Re-verify the fingerprint immediately before finish
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
- Only / review passes completed; the partial review was not…
- Review receipt and PR coverage manifest fingerprints differ
- Android loaded-image identity changed: /proc/self/maps has…
- Android loaded-image proof changed from
- cancel wins race
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)