{"record":{"id":"afaca4a9e2c6e4c3","repo":"zed-industries/zed","slug":"is-in-non-existent-deleted-hunk","errorCode":null,"errorMessage":"{:?} is in non-existent deleted hunk","messagePattern":"(.+?) is in non-existent deleted hunk","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/multi_buffer/src/multi_buffer.rs","lineNumber":4637,"sourceCode":"    }\n\n    pub fn max_row(&self) -> MultiBufferRow {\n        MultiBufferRow(self.text_summary().lines.row)\n    }\n\n    pub fn text_summary(&self) -> MBTextSummary {\n        self.diff_transforms.summary().output\n    }\n\n    pub fn text_summary_for_range<MBD, O>(&self, range: Range<O>) -> MBD\n    where\n        MBD: MultiBufferDimension + AddAssign,\n        O: ToOffset,\n    {\n        let range = range.start.to_offset(self)..range.end.to_offset(self);\n        let mut cursor = self\n            .diff_transforms\n            .cursor::<Dimensions<MultiBufferOffset, ExcerptOffset>>(());\n        cursor.seek(&range.start, Bias::Right);\n\n        let Some(first_transform) = cursor.item() else {\n            return MBD::from_summary(&MBTextSummary::default());\n        };\n\n        let diff_transform_start = cursor.start().0;\n        let diff_transform_end = cursor.end().0;\n        let diff_start = range.start;\n        let start_overshoot = diff_start - diff_transform_start;\n        let end_overshoot = std::cmp::min(range.end, diff_transform_end) - diff_transform_start;\n\n        let mut result = match first_transform {\n            DiffTransform::BufferContent { .. } => {\n                let excerpt_start = cursor.start().1 + start_overshoot;\n                let excerpt_end = cursor.start().1 + end_overshoot;\n                self.text_summary_for_excerpt_offset_range(excerpt_start..excerpt_end)\n            }","sourceCodeStart":4619,"sourceCodeEnd":4655,"githubUrl":"https://github.com/zed-industries/zed/blob/5a9b9558db01a6b906cec2fb70a797affdc58cdd/crates/multi_buffer/src/multi_buffer.rs#L4619-L4655","documentation":"When a multibuffer renders a diff, deleted (base-only) hunks become DiffTransform::DeletedHunk entries whose text lives in the diff's base text. Computing a text summary for a range that intersects such a hunk requires that base text; if diff_state(buffer_id) no longer provides one, the code panics with '<range start> is in non-existent deleted hunk'. The snapshot still contains deleted-hunk geometry for a buffer whose diff base was dropped or replaced.","triggerScenarios":"Calling summary_for_range-style APIs (used by layout, scrolling, offset math) over a range crossing a DeletedHunk after the buffer's diff base was removed - the diff was invalidated, the base buffer closed, or the snapshot predates a diff rebuild that dropped base text.","commonSituations":"Races between git changes on disk and in-memory diff bases; closing the base/diff buffer while a snapshot with deleted hunks is still used for layout; partial updates that strip diff bases without recomputing the transforms that reference them.","solutions":["Obtain a new multibuffer snapshot after any diff/base-text change and recompute summaries from it","Keep the diff base alive (retain the base buffer in the diff state) for as long as snapshots containing DeletedHunk transforms are in use","When invalidating diffs, clear or replace the deleted-hunk transforms in the same edit so stale geometry cannot be queried","If it reproduces, report it upstream with steps - this is an internal consistency invariant"],"exampleFix":"// before: summary computed on a snapshot whose diff base is gone\nlet summary = old_snapshot.text_summary_for_range(range); // panics inside DeletedHunk\n\n// after: refresh the snapshot after diff invalidation, then compute\napply_diff_invalidation(&mut multi_buffer, cx);\nlet snapshot = multi_buffer.read(cx).snapshot(cx);\nlet summary = snapshot.text_summary_for_range(range);","handlingStrategy":"validation","validationCode":"// never query summaries on a snapshot older than the last diff update:\n// re-snapshot right before summarizing ranges that can cross deleted hunks\nlet snapshot = multi_buffer.read(cx).snapshot(cx);\nlet summary = snapshot.text_summary_for_range(range);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Invalidate cached summaries and layout state whenever a buffer's diff state changes","Keep diff bases alive as long as snapshots containing DeletedHunk transforms are in use","Apply diff-base removal and transform recomputation in one atomic edit so stale geometry is never observable"],"tags":["zed","multi-buffer","diff","text-summary","panic"],"backgroundTag":"stale-diff-base","analyzedSha":"5a9b9558db01a6b906cec2fb70a797affdc58cdd","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}