{"record":{"id":"5c3199ec7578cd5b","repo":"Hmbown/CodeWhale","slug":"only-review-passes-completed-the-partial-review-was-not","errorCode":null,"errorMessage":"Only {}/{} review passes completed; the partial review was not accepted or posted.","messagePattern":"Only (.+?)/(.+?) review passes completed; the partial review was not accepted or posted\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/review.rs","lineNumber":724,"sourceCode":"                && self.manifest.passes.get(expected - 1) == Some(&pass.manifest),\n            \"Review pass arrived out of order or does not match the immutable manifest; expected pass {expected}\"\n        );\n        let output = ReviewOutput::from_structured_str(&raw).ok_or_else(|| {\n            anyhow::anyhow!(\n                \"Review pass {expected}/{} did not return valid structured JSON; the partial review was not accepted or posted.\",\n                self.manifest.passes.len()\n            )\n        })?;\n        self.outputs.push(output);\n        self.raw_outputs.push(raw);\n        Ok(())\n    }\n\n    pub(crate) fn finish(\n        self,\n        complete_diff: &str,\n    ) -> anyhow::Result<(ReviewOutput, String, ReviewReceiptCoverage)> {\n        anyhow::ensure!(\n            self.outputs.len() == self.manifest.passes.len(),\n            \"Only {}/{} review passes completed; the partial review was not accepted or posted.\",\n            self.outputs.len(),\n            self.manifest.passes.len()\n        );\n        anyhow::ensure!(\n            diff_fingerprint(complete_diff) == self.manifest.diff_fingerprint,\n            \"Complete PR diff fingerprint changed before review aggregation\"\n        );\n        let mut issues = Vec::new();\n        let mut suggestions = Vec::new();\n        let mut summaries = Vec::new();\n        let mut assessments = Vec::new();\n        for (index, output) in self.outputs.into_iter().enumerate() {\n            if !output.summary.is_empty() {\n                summaries.push(format!(\"Pass {}: {}\", index + 1, output.summary));\n            }\n            if !output.overall_assessment.is_empty() {","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/review.rs#L706-L742","documentation":"`ReviewAggregation::finish` guards the PR review pipeline against posting a partial review. Before merging per-pass outputs into a `ReviewOutput`, it asserts that the number of collected pass outputs equals the number of passes declared in the manifest; if any pass was skipped, failed, or returned early, the review is rejected instead of being accepted or posted with incomplete coverage.","triggerScenarios":"Calling `finish(complete_diff)` on an aggregator whose `outputs` vec has fewer entries than `manifest.passes.len()` — e.g. a review pass errored, was skipped due to a missing diff for its slice, or an agent loop dropped one of the planned passes.","commonSituations":"A subagent pass timed out or panicked and its output was not pushed; a manifest was built for N passes but only N-1 succeeded and the code called `finish` anyway; partial-result handling drifted after a refactor of the pass loop.","solutions":["Inspect the logs for the failed/skipped review pass and fix its root cause, then re-run the review","Ensure every planned pass's output is pushed to the aggregator (including defensive pushes on error paths) before calling `finish`","Re-run the whole review from scratch so all manifest passes complete"],"exampleFix":"// before\nif outputs.len() < manifest.passes.len() { finish(outputs.as_slice(), &complete_diff)?; }\n// after\nanyhow::ensure!(outputs.len() == manifest.passes.len(), \"missing review pass outputs\");\nlet (review, diff, coverage) = finish(outputs, &complete_diff)?;","handlingStrategy":"validation","validationCode":"if aggregator.outputs.len() != aggregator.manifest.passes.len() { return Err(anyhow!(\"review incomplete: {}/{} passes\", aggregator.outputs.len(), aggregator.manifest.passes.len())); }","typeGuard":null,"tryCatchPattern":"match aggregator.finish(&complete_diff) { Ok((review, diff, coverage)) => post(review), Err(e) => { log::error!(\"review rejected: {e}\"); restart_full_review(); } }","preventionTips":["Push an output (or explicit failure marker) for every planned pass before calling finish","Wrap the pass loop so a pass error aborts the whole review rather than silently skipping","Add a debug assertion comparing outputs.len() and manifest.passes.len() in tests"],"tags":["review","pr","invariant-violation","partial-result"],"backgroundTag":"internal-invariant-violation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}