From 9f58770392a6bb37837526e24c28c94f7d957a62 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Wed, 25 Mar 2026 10:16:42 +0100 Subject: [PATCH] agent: Add a regression test to ensure title updates do not loop (#52395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Follow up to #52388 ## How to Review ## 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 Release Notes: - N/A --- crates/agent/src/agent.rs | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index e65ec23f107ea7caa9f7004d630f79d1c0c7409e..f36d8c0497430c27c7cafd99445c8baad18406f5 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -3002,6 +3002,70 @@ mod internal_tests { }); } + #[gpui::test] + async fn test_rapid_title_changes_do_not_loop(cx: &mut TestAppContext) { + // Regression test: rapid title changes must not cause a propagation loop + // between Thread and AcpThread via handle_thread_title_updated. + init_test(cx); + let fs = FakeFs::new(cx.executor()); + fs.insert_tree("/", json!({ "a": {} })).await; + let project = Project::test(fs.clone(), [], cx).await; + let thread_store = cx.new(|cx| ThreadStore::new(cx)); + let agent = cx.update(|cx| { + NativeAgent::new(thread_store.clone(), Templates::new(), None, fs.clone(), cx) + }); + let connection = Rc::new(NativeAgentConnection(agent.clone())); + + let acp_thread = cx + .update(|cx| { + connection + .clone() + .new_session(project.clone(), PathList::new(&[Path::new("")]), cx) + }) + .await + .unwrap(); + + let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone()); + let thread = agent.read_with(cx, |agent, _| { + agent.sessions.get(&session_id).unwrap().thread.clone() + }); + + let title_updated_count = Rc::new(std::cell::RefCell::new(0usize)); + cx.update(|cx| { + let count = title_updated_count.clone(); + cx.subscribe( + &thread, + move |_entity: Entity, _event: &TitleUpdated, _cx: &mut App| { + let new_count = { + let mut count = count.borrow_mut(); + *count += 1; + *count + }; + assert!( + new_count <= 2, + "TitleUpdated fired {new_count} times; \ + title updates are looping" + ); + }, + ) + .detach(); + }); + + thread.update(cx, |thread, cx| thread.set_title("first".into(), cx)); + thread.update(cx, |thread, cx| thread.set_title("second".into(), cx)); + + cx.run_until_parked(); + + thread.read_with(cx, |thread, _| { + assert_eq!(thread.title(), Some("second".into())); + }); + acp_thread.read_with(cx, |acp_thread, _| { + assert_eq!(acp_thread.title(), Some("second".into())); + }); + + assert_eq!(*title_updated_count.borrow(), 2); + } + fn thread_entries( thread_store: &Entity, cx: &mut TestAppContext,