Conversation
Contributor
Veykril
approved these changes
Feb 14, 2026
maxdeviant
added a commit
that referenced
this pull request
Feb 15, 2026
5 tasks
rtfeldman
pushed a commit
that referenced
this pull request
Feb 17, 2026
…rs (#49047) ## Bug **Sentry:** [ZED-3W3](https://sentry.io/organizations/zed-dev/issues/7085300207/) (13 events, first seen 2025-12-03) `summaries_for_anchors` panics with "cannot seek backward" when iterating selection anchors whose excerpt locators are no longer in ascending order after an `update_path_excerpts` call. ### Root cause `update_path_excerpts` merges new excerpt ranges with existing ones. When an existing excerpt has no overlap with any new range (e.g. `existing_range.end < new.context.start`), it is removed from the excerpts tree but is **not** recorded in `replaced_excerpts`. Its stale locator persists in the `excerpt_ids` tree. Later, when a new excerpt is inserted for a different path whose locator falls between the stale locator and another surviving excerpt, `summaries_for_anchors` can encounter anchors in an order where it needs to seek the cursor *backward* — violating the forward-only cursor invariant. Concretely, the scenario requires: 1. A path with ≥3 excerpts (E_B1, E_B2, E_B3) with anchors in E_B2 and E_B3. 2. An `update_path_excerpts` call that keeps E_B1, **removes E_B2** (no overlap → no `replaced_excerpts` entry), and replaces E_B3 with a new excerpt N. 3. A new path D inserted between paths B and C, whose excerpt E_D gets a locator between N and E_B2's stale locator. 4. Resolving the old anchors: E_B2's stale locator seeks past E_D, then E_B3→N tries to seek backward to N — **panic**. ## Fix In the two branches of `update_path_excerpts` where existing excerpts are removed without being absorbed into a new range, map the removed excerpt to the nearest surviving neighbor (the last kept or inserted excerpt) in `replaced_excerpts`. This ensures stale anchors always remap to a valid, correctly-ordered locator. The two branches are: - `existing_range.end < new.context.start` — existing excerpt falls entirely before the next new range - `(None, Some(...))` — no more new ranges for remaining existing excerpts ## Verification - A reproduction test (`test_cannot_seek_backward_after_excerpt_replacement`) that constructs the exact crash scenario now passes. - Full `multi_buffer` test suite (48 tests) passes with no regressions. - Clippy is clean. Release Notes: - Fixed a possible crash when updating excerpts in multibuffers
rtfeldman
pushed a commit
that referenced
this pull request
Feb 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
Sentry: ZED-3W3 (13 events, first seen 2025-12-03)
summaries_for_anchorspanics with "cannot seek backward" when iterating selection anchors whose excerpt locators are no longer in ascending order after anupdate_path_excerptscall.Root cause
update_path_excerptsmerges new excerpt ranges with existing ones. When an existing excerpt has no overlap with any new range (e.g.existing_range.end < new.context.start), it is removed from the excerpts tree but is not recorded inreplaced_excerpts. Its stale locator persists in theexcerpt_idstree.Later, when a new excerpt is inserted for a different path whose locator falls between the stale locator and another surviving excerpt,
summaries_for_anchorscan encounter anchors in an order where it needs to seek the cursor backward — violating the forward-only cursor invariant.Concretely, the scenario requires:
update_path_excerptscall that keeps E_B1, removes E_B2 (no overlap → noreplaced_excerptsentry), and replaces E_B3 with a new excerpt N.Fix
In the two branches of
update_path_excerptswhere existing excerpts are removed without being absorbed into a new range, map the removed excerpt to the nearest surviving neighbor (the last kept or inserted excerpt) inreplaced_excerpts. This ensures stale anchors always remap to a valid, correctly-ordered locator.The two branches are:
existing_range.end < new.context.start— existing excerpt falls entirely before the next new range(None, Some(...))— no more new ranges for remaining existing excerptsVerification
test_cannot_seek_backward_after_excerpt_replacement) that constructs the exact crash scenario now passes.multi_buffertest suite (48 tests) passes with no regressions.Release Notes: