{"record":{"id":"e78ac864513f0130","repo":"Hmbown/CodeWhale","slug":"pr-review-plan-did-not-partition-the-complete-diff-byte-for","errorCode":null,"errorMessage":"PR review plan did not partition the complete diff byte-for-byte","messagePattern":"PR review plan did not partition the complete diff byte-for-byte","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/review.rs","lineNumber":532,"sourceCode":"                rebuilt.push_str(&piece.diff[piece.header_bytes..]);\n                pieces.push(piece);\n            }\n            PrReviewAtom::Skipped {\n                patch,\n                label,\n                chars,\n                reason,\n            } => {\n                rebuilt.push_str(patch);\n                skipped.push(PrReviewSkippedFile {\n                    file: label,\n                    reason: reason.to_string(),\n                    chars,\n                });\n            }\n        }\n    }\n    anyhow::ensure!(\n        rebuilt == diff,\n        \"PR review plan did not partition the complete diff byte-for-byte\"\n    );\n\n    let mut grouped: Vec<Vec<PrReviewPiece<'_>>> = Vec::new();\n    let mut current: Vec<PrReviewPiece<'_>> = Vec::new();\n    let mut current_chars = 0;\n    for piece in pieces {\n        let piece_chars = super::review_pr::model_diff(&piece.diff).chars().count();\n        if !current.is_empty() && current_chars + piece_chars > max_chars {\n            grouped.push(std::mem::take(&mut current));\n            current_chars = 0;\n        }\n        current.push(piece);\n        current_chars += piece_chars;\n    }\n    if !current.is_empty() {\n        grouped.push(current);","sourceCodeStart":514,"sourceCodeEnd":550,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/review.rs#L514-L550","documentation":"plan_pr_review reassembles all planned pieces and requires the concatenation to equal the original diff exactly (byte-for-byte). This is an internal invariant: the plan must partition the entire diff without dropping or altering any bytes. Failure means a piece was skipped, reordered, or mutated.","triggerScenarios":"A code change to the hunk-splitting or grouping logic drops or rewrites bytes; oversized-file splitting cuts at a non-boundary; pieces for skipped files are omitted but the rebuild check still runs.","commonSituations":"Regressions after modifying PrReviewPiece building or splitting logic; diffs with CRLF or unusual encodings mishandled by the rebuild path; hunk-boundary arithmetic off by one.","solutions":["Update to a version with the splitting fix if you patched the planner locally","Fix the splitter to cut only at complete hunk boundaries and never drop bytes","Verify diff encoding/newlines are preserved through the piece pipeline","Run the pr_batch_plan_* tests to reproduce which files lose bytes"],"exampleFix":"// before\nlet cut = hunk_start + max_chars; // arbitrary byte cut\n// after\nlet cut = last_complete_hunk_end_within(max_chars); // boundary-preserving cut","handlingStrategy":"validation","validationCode":"debug_assert_eq!(pieces.iter().map(|p| p.text.len()).sum::<usize>(), diff.len());","typeGuard":null,"tryCatchPattern":"match plan_pr_review(...) { Err(e) if e.to_string().contains(\"byte-for-byte\") => { log pieces; return Err(e); } ... }","preventionTips":["Cut oversized files only at complete hunk boundaries","Preserve CRLF/encoding through the piece pipeline","Run pr_batch_plan_* tests after any splitter change"],"tags":["invariant","review","parsing"],"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"}