{"record":{"id":"f839b13df3515aab","repo":"astral-sh/ruff","slug":"patch-span-bigger-is-beyond-the-end-of-buffe","errorCode":null,"errorMessage":"Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`","messagePattern":"Patch span `(.+?)` is beyond the end of buffer `(.+?)`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_annotate_snippets/src/renderer/source_map.rs","lineNumber":443,"sourceCode":"                    }\n                }\n                // If high index is None\n                if hi_opt.is_none() {\n                    buf.push('\\n');\n                }\n            }\n        }\n\n        let source_len = self.source.len();\n        if let Some(bigger) = patches.iter().find_map(|x| {\n            // Allow patching one past the last character in the source.\n            if source_len + 1 < x.span.end {\n                Some(&x.span)\n            } else {\n                None\n            }\n        }) {\n            panic!(\"Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`\")\n        }\n\n        // Assumption: all spans are in the same file, and all spans\n        // are disjoint. Sort in ascending order.\n        patches.sort_by_key(|p| p.span.start);\n\n        // Find the bounding span.\n        let (lo, hi) = if fold {\n            let lo = patches\n                .iter()\n                .map(|p| p.span.clone())\n                .min_by_key(|s| s.start)?;\n            let hi = patches\n                .iter()\n                .map(|p| p.span.clone())\n                .max_by_key(|s| s.end)?;\n            (lo, hi)\n        } else {","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ruff_annotate_snippets/src/renderer/source_map.rs#L425-L461","documentation":"The patch (suggestion/diff) rendering path validates every patch span against the snippet source before splicing. Like annotations, an end up to source.len() + 1 is allowed ('patching one past the last character'); beyond that it panics with the patch range and buffer length. It means the patch spans were computed against different (usually older or longer) text than the source being rendered.","triggerScenarios":"Rendering a snippet with a suggestion whose patch spans came from a different version of the file: edits landed between diagnostic creation and rendering, or test fixtures were updated while expected ranges stayed stale.","commonSituations":"LSP/watch-mode rendering after document edits; tests with hardcoded patch offsets; transformations that splice the source without recomputing ranges.","solutions":["Recompute patch spans from the exact text passed as the snippet source.","Clamp or drop patches whose end exceeds source.len() + 1 before rendering.","In watch-mode flows, discard cached suggestions when the underlying file changes."],"exampleFix":"// before: patch range computed against an older, longer version of the file\nlet patch = Patch::new(stale_range, replacement); // stale_range.end > source.len()\n\n// after: derive the range from the text being rendered\nlet patch = Patch::new(current_range, replacement); // current_range.end <= source.len()","handlingStrategy":"validation","validationCode":"fn patches_within(source: &str, patches: &[Patch<'_>]) -> bool {\n    let len = source.len();\n    patches.iter().all(|p| p.span.end <= len + 1 && p.span.start <= p.span.end)\n}\n\n// before rendering a suggestion:\ndebug_assert!(patches_within(snippet_source, patches));","typeGuard":null,"tryCatchPattern":"let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| renderer.render(&report)))\n    .unwrap_or_else(|_| render_without_suggestion(&report)); // drop the diff, keep the message","preventionTips":["Compute patch spans from the exact text used for rendering, after any edits settle.","Invalidate cached suggestions when the underlying file changes.","Keep expected patch ranges in fixtures generated from the fixture text, not typed by hand."],"tags":["diagnostics","rendering","panic","patch","diff","out-of-range"],"backgroundTag":"span-out-of-range","analyzedSha":"d1087a4b9e03d253a88703f34e0869ee4b805456","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}