Sometimes the action log would not auto-accept agent edits when commiting. Gpt-5.4 identified this race condition: This fixes a race where `keep_committed_edits` could run after `head_commit` changed but before the new git base text had been applied, leaving committed agent edits marked as unreviewed; `ActionLog` now waits for an explicit `BufferDiffEvent::BaseTextChanged` instead of inferring readiness from generic `DiffChanged` activity, so it only accepts edits after the diff base itself is actually updated. - `ReloadGitState` updates `head_commit` before `ReloadBufferDiffBases` finishes loading and applying the new HEAD text. - In that gap, an unrelated `DiffChanged` can fire from a normal diff recalculation. - The old logic treated that event as the commit signal and ran `keep_committed_edits` too early. - `keep_committed_edits` then read stale diff base text, so it failed to match the committed agent edits. - When the real base-text update arrived later, the HEAD had already been overwritten (`old_head`), and the edits stayed unreviewed. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - Fixed an issue where committing agent written code would sometimes not mark edits as accepted
Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 4 files). Show all
Unified · Split
| @@ -1515,11 +1515,17 @@ pub struct DiffChanged { · 1514 unmodified lines | ||
| 1515 | 1515 | |
| 1516 | 1516 | #[derive(Clone, Debug)] |
| 1517 | 1517 | pub enum BufferDiffEvent { |
| 1518 | + BaseTextChanged, | |
| 1518 | 1519 | DiffChanged(DiffChanged), |
| 1519 | 1520 | LanguageChanged, |
| 1520 | 1521 | HunksStagedOrUnstaged(Option<Rope>), |
| 1521 | 1522 | } |
| 1522 | 1523 | |
| 1524 | +struct SetSnapshotResult { | |
| 1525 | + change: DiffChanged, | |
| 1526 | + base_text_changed: bool, | |
| 1527 | +} | |
| 1528 | + | |
| 1523 | 1529 | impl EventEmitter<BufferDiffEvent> for BufferDiff {} |
| 1524 | 1530 | |
| 1525 | 1531 | impl BufferDiff { |
| @@ -1784,7 +1790,7 @@ impl BufferDiff { · 258 unmodified lines | ||
| 1784 | 1790 | secondary_diff_change: Option<Range<Anchor>>, |
| 1785 | 1791 | clear_pending_hunks: bool, |
| 1786 | 1792 | cx: &mut Context<Self>, |
| 1787 | − ) -> impl Future<Output = DiffChanged> + use<> { | |
| 1793 | + ) -> impl Future<Output = SetSnapshotResult> + use<> { | |
| 1788 | 1794 | log::debug!("set snapshot with secondary {secondary_diff_change:?}"); |
| 1789 | 1795 | |
| 1790 | 1796 | let old_snapshot = self.snapshot(cx); |
| @@ -1904,10 +1910,13 @@ impl BufferDiff { · 113 unmodified lines | ||
| 1904 | 1910 | if let Some(parsing_idle) = parsing_idle { |
| 1905 | 1911 | parsing_idle.await; |
| 1906 | 1912 | } |
| 1907 | − DiffChanged { | |
| 1908 | − changed_range, | |
| 1909 | − base_text_changed_range, | |
| 1910 | − extended_range, | |
| 1913 | + SetSnapshotResult { | |
| 1914 | + change: DiffChanged { | |
| 1915 | + changed_range, | |
| 1916 | + base_text_changed_range, | |
| 1917 | + extended_range, | |
| 1918 | + }, | |
| 1919 | + base_text_changed, | |
| 1911 | 1920 | } |
| 1912 | 1921 | } |
| 1913 | 1922 | } |
| @@ -1938,12 +1947,15 @@ impl BufferDiff { · 24 unmodified lines | ||
| 1938 | 1947 | ); |
| 1939 | 1948 | |
| 1940 | 1949 | cx.spawn(async move |this, cx| { |
| 1941 | − let change = fut.await; | |
| 1950 | + let result = fut.await; | |
| 1942 | 1951 | this.update(cx, |_, cx| { |
| 1943 | − cx.emit(BufferDiffEvent::DiffChanged(change.clone())); | |
| 1952 | + if result.base_text_changed { | |
| 1953 | + cx.emit(BufferDiffEvent::BaseTextChanged); | |
| 1954 | + } | |
| 1955 | + cx.emit(BufferDiffEvent::DiffChanged(result.change.clone())); | |
| 1944 | 1956 | }) |
| 1945 | 1957 | .ok(); |
| 1946 | − change.changed_range | |
| 1958 | + result.change.changed_range | |
| 1947 | 1959 | }) |
| 1948 | 1960 | } |
| 1949 | 1961 | |
| @@ -2019,8 +2031,11 @@ impl BufferDiff { · 69 unmodified lines | ||
| 2019 | 2031 | let fg_executor = cx.foreground_executor().clone(); |
| 2020 | 2032 | let snapshot = fg_executor.block_on(fut); |
| 2021 | 2033 | let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx); |
| 2022 | − let change = fg_executor.block_on(fut); | |
| 2023 | − cx.emit(BufferDiffEvent::DiffChanged(change)); | |
| 2034 | + let result = fg_executor.block_on(fut); | |
| 2035 | + if result.base_text_changed { | |
| 2036 | + cx.emit(BufferDiffEvent::BaseTextChanged); | |
| 2037 | + } | |
| 2038 | + cx.emit(BufferDiffEvent::DiffChanged(result.change)); | |
| 2024 | 2039 | } |
| 2025 | 2040 | |
| 2026 | 2041 | pub fn base_text_buffer(&self) -> &Entity<language::Buffer> { |