astral-sh/ruff · error

Patch span `{bigger:?}` is beyond the end of buffer `{source

Error message

Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`

What it means

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.

Source

Thrown at crates/ruff_annotate_snippets/src/renderer/source_map.rs:443

                    }
                }
                // If high index is None
                if hi_opt.is_none() {
                    buf.push('\n');
                }
            }
        }

        let source_len = self.source.len();
        if let Some(bigger) = patches.iter().find_map(|x| {
            // Allow patching one past the last character in the source.
            if source_len + 1 < x.span.end {
                Some(&x.span)
            } else {
                None
            }
        }) {
            panic!("Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`")
        }

        // Assumption: all spans are in the same file, and all spans
        // are disjoint. Sort in ascending order.
        patches.sort_by_key(|p| p.span.start);

        // Find the bounding span.
        let (lo, hi) = if fold {
            let lo = patches
                .iter()
                .map(|p| p.span.clone())
                .min_by_key(|s| s.start)?;
            let hi = patches
                .iter()
                .map(|p| p.span.clone())
                .max_by_key(|s| s.end)?;
            (lo, hi)
        } else {

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Recompute patch spans from the exact text passed as the snippet source.
  2. Clamp or drop patches whose end exceeds source.len() + 1 before rendering.
  3. In watch-mode flows, discard cached suggestions when the underlying file changes.

Example fix

// before: patch range computed against an older, longer version of the file
let patch = Patch::new(stale_range, replacement); // stale_range.end > source.len()

// after: derive the range from the text being rendered
let patch = Patch::new(current_range, replacement); // current_range.end <= source.len()
Defensive patterns

Strategy: validation

Validate before calling

fn patches_within(source: &str, patches: &[Patch<'_>]) -> bool {
    let len = source.len();
    patches.iter().all(|p| p.span.end <= len + 1 && p.span.start <= p.span.end)
}

// before rendering a suggestion:
debug_assert!(patches_within(snippet_source, patches));

Try / catch

let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| renderer.render(&report)))
    .unwrap_or_else(|_| render_without_suggestion(&report)); // drop the diff, keep the message

Prevention

When it happens

Trigger: 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.

Common situations: LSP/watch-mode rendering after document edits; tests with hardcoded patch offsets; transformations that splice the source without recomputing ranges.

Related errors


AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20). Data as JSON: /api/errors/f839b13df3515aab. Report an issue: GitHub.