{"record":{"id":"92349599a6f7894e","repo":"Hmbown/CodeWhale","slug":"review-max-passes-must-be-from-1-to-max-review-passes","errorCode":null,"errorMessage":"Review max_passes must be from 1 to {MAX_REVIEW_PASSES}","messagePattern":"Review max_passes must be from 1 to (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/review.rs","lineNumber":432,"sourceCode":"    },\n}\n\n/// Plan PR review passes over `diff`, degrading instead of failing closed\n/// (#6285 AC3): files that fit no pass and passes beyond `max_passes` are\n/// skipped in diff order and named in `manifest.skipped_files` (AC4). Only a\n/// plan that covers nothing still errors.\n///\n/// Known limitations, beside the behaviour: skips are whole files — a file\n/// with one oversized hunk is skipped entirely, never truncated — and files\n/// stay in diff order rather than re-sorted by estimated risk.\npub(crate) fn plan_pr_review(\n    diff: &str,\n    view: &super::review_pr::GhPullRequest,\n    max_chars: usize,\n    max_passes: usize,\n) -> anyhow::Result<PrReviewPlan> {\n    anyhow::ensure!(max_chars > 0, \"Review max_chars must be positive\");\n    anyhow::ensure!(\n        (1..=MAX_REVIEW_PASSES).contains(&max_passes),\n        \"Review max_passes must be from 1 to {MAX_REVIEW_PASSES}\"\n    );\n    let patches = pr_file_patches(diff);\n    anyhow::ensure!(\n        patches.len() == view.changed_files && !patches.is_empty(),\n        \"Complete PR review plan found {} file patches; expected {}\",\n        patches.len(),\n        view.changed_files\n    );\n\n    // A whole file stays together whenever it fits. An oversized text file\n    // splits only at complete hunk boundaries, with the full file header\n    // replayed into every part so each part stays a self-describing patch;\n    // no line is elided, shortened or reordered. Sizes use the model\n    // representation, so a binary payload already omitted there can never\n    // drive a split.\n    let mut atoms: Vec<PrReviewAtom<'_>> = Vec::new();","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/review.rs#L414-L450","documentation":"plan_pr_review parameter validation: max_passes must lie between 1 and MAX_REVIEW_PASSES; out-of-bound values are rejected before any pass planning, keeping the degradation manifest's pass count bounded.","triggerScenarios":"Calling plan_pr_review with max_passes = 0 or exceeding MAX_REVIEW_PASSES, usually from config (review.max_passes) or a computed pass count.","commonSituations":"Typo in config (max_passes: 0); user setting an unbounded pass count; version change where MAX_REVIEW_PASSES was lowered below a stored config value.","solutions":["Set max_passes to 1..=MAX_REVIEW_PASSES in config","Clamp at the call site: `let max_passes = max_passes.clamp(1, MAX_REVIEW_PASSES);`","Check stored config against the current MAX_REVIEW_PASSES constant after upgrading"],"exampleFix":"// before\nlet max_passes = 0;\nplan_pr_review(diff, &view, max_chars, max_passes)?;\n// after\nlet max_passes = max_passes.clamp(1, MAX_REVIEW_PASSES);\nplan_pr_review(diff, &view, max_chars, max_passes)?;","handlingStrategy":"validation","validationCode":"if !(1..=MAX_REVIEW_PASSES).contains(&max_passes) { return Err(anyhow!(\"max_passes out of range\")); }","typeGuard":null,"tryCatchPattern":"match plan_pr_review(...) { Err(e) if e.to_string().contains(\"max_passes\") => clamp_and_retry(1, MAX_REVIEW_PASSES), ... }","preventionTips":["Clamp config values with .clamp(1, MAX_REVIEW_PASSES) at load time","Validate review config once at startup, not per call","Re-check stored config after MAX_REVIEW_PASSES changes between versions"],"tags":["validation","arguments","review"],"backgroundTag":"value-out-of-range","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"}