agent: Block agent v2 feature on stable release channel (#53849)

Anthony Eid and Max Brunsfeld created

Based on a partial revert of: 7bcdb12b4c5e9de0e4632c770336799e7c8d13d6

The main difference is instead of a feature flag we now check
`ReleaseChannel::Stable` != current_release_channel when the UI checks
if agent v2 (sidebar) features should be enabled.

Self-Review Checklist:

- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- N/A or Added/Fixed/Improved ...

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

Cargo.lock                                  |   2 
crates/agent_ui/src/agent_panel.rs          | 106 +++++++++++++++++++++-
crates/agent_ui/src/agent_ui.rs             |  41 ++++++++
crates/auto_update_ui/src/auto_update_ui.rs | 103 +++++++++++-----------
crates/feature_flags/src/flags.rs           |  10 ++
crates/settings_ui/src/page_data.rs         |  49 +++++----
crates/workspace/Cargo.toml                 |   2 
crates/workspace/src/multi_workspace.rs     |   4 
crates/workspace/src/persistence.rs         |  46 ---------
9 files changed, 232 insertions(+), 131 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -21659,7 +21659,6 @@ dependencies = [
  "collections",
  "component",
  "db",
- "feature_flags",
  "fs",
  "futures 0.3.32",
  "git",
@@ -21675,6 +21674,7 @@ dependencies = [
  "postage",
  "pretty_assertions",
  "project",
+ "release_channel",
  "remote",
  "schemars",
  "serde",

crates/agent_ui/src/agent_panel.rs 🔗

@@ -71,6 +71,7 @@ use project::git_store::{GitStoreEvent, RepositoryEvent};
 use project::project_settings::ProjectSettings;
 use project::{Project, ProjectPath, Worktree, WorktreePaths, linked_worktree_short_name};
 use prompt_store::{PromptStore, UserPromptId};
+use release_channel::ReleaseChannel;
 use remote::RemoteConnectionOptions;
 use rules_library::{RulesLibrary, open_rules_library};
 use settings::TerminalDockPosition;
@@ -93,6 +94,10 @@ const AGENT_PANEL_KEY: &str = "agent_panel";
 const MIN_PANEL_WIDTH: Pixels = px(300.);
 const RECENTLY_UPDATED_MENU_LIMIT: usize = 6;
 const LAST_USED_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
+
+fn agent_v2_enabled(cx: &App) -> bool {
+    !matches!(ReleaseChannel::try_global(cx), Some(ReleaseChannel::Stable))
+}
 /// Maximum number of idle threads kept in the agent panel's retained list.
 /// Set as a GPUI global to override; otherwise defaults to 5.
 pub struct MaxIdleRetainedThreads(pub usize);
@@ -1006,10 +1011,10 @@ impl AgentPanel {
                                 StartThreadIn::LocalProject => true,
                                 StartThreadIn::NewWorktree { .. } => {
                                     let project = panel.project.read(cx);
-                                    !project.is_via_collab()
+                                    agent_v2_enabled(cx) && !project.is_via_collab()
                                 }
                                 StartThreadIn::LinkedWorktree { path, .. } => {
-                                    path.exists()
+                                    agent_v2_enabled(cx) && path.exists()
                                 }
                             };
                             if is_valid {
@@ -2547,6 +2552,9 @@ impl AgentPanel {
         let new_target = match action {
             StartThreadIn::LocalProject => StartThreadIn::LocalProject,
             StartThreadIn::NewWorktree { .. } => {
+                if !agent_v2_enabled(cx) {
+                    return;
+                }
                 if !self.project_has_git_repository(cx) {
                     log::error!(
                         "set_start_thread_in: cannot use worktree mode without a git repository"
@@ -2562,6 +2570,9 @@ impl AgentPanel {
                 action.clone()
             }
             StartThreadIn::LinkedWorktree { .. } => {
+                if !agent_v2_enabled(cx) {
+                    return;
+                }
                 if !self.project_has_git_repository(cx) {
                     log::error!(
                         "set_start_thread_in: cannot use LinkedWorktree without a git repository"
@@ -2586,6 +2597,10 @@ impl AgentPanel {
     }
 
     fn cycle_start_thread_in(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        if !agent_v2_enabled(cx) {
+            return;
+        }
+
         let next = match &self.start_thread_in {
             StartThreadIn::LocalProject => StartThreadIn::NewWorktree {
                 worktree_name: None,
@@ -2600,6 +2615,15 @@ impl AgentPanel {
 
     fn reset_start_thread_in_to_default(&mut self, cx: &mut Context<Self>) {
         use settings::{NewThreadLocation, Settings};
+        if !agent_v2_enabled(cx) {
+            if self.start_thread_in != StartThreadIn::LocalProject {
+                self.start_thread_in = StartThreadIn::LocalProject;
+                self.serialize(cx);
+                cx.notify();
+            }
+            return;
+        }
+
         let default = AgentSettings::get_global(cx).new_thread_location;
         let start_thread_in = match default {
             NewThreadLocation::LocalProject => StartThreadIn::LocalProject,
@@ -2622,6 +2646,15 @@ impl AgentPanel {
     }
 
     fn sync_start_thread_in_with_git_state(&mut self, cx: &mut Context<Self>) {
+        if !agent_v2_enabled(cx) {
+            if self.start_thread_in != StartThreadIn::LocalProject {
+                self.start_thread_in = StartThreadIn::LocalProject;
+                self.serialize(cx);
+                cx.notify();
+            }
+            return;
+        }
+
         if matches!(self.start_thread_in, StartThreadIn::LocalProject) {
             return;
         }
@@ -4108,6 +4141,47 @@ impl AgentPanel {
             })
     }
 
+    fn render_recent_entries_menu(
+        &self,
+        icon: IconName,
+        corner: Corner,
+        cx: &mut Context<Self>,
+    ) -> impl IntoElement {
+        let focus_handle = self.focus_handle(cx);
+
+        PopoverMenu::new("agent-nav-menu")
+            .trigger_with_tooltip(
+                IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
+                {
+                    move |_window, cx| {
+                        Tooltip::for_action_in(
+                            "Toggle Recently Updated Threads",
+                            &ToggleNavigationMenu,
+                            &focus_handle,
+                            cx,
+                        )
+                    }
+                },
+            )
+            .anchor(corner)
+            .with_handle(self.agent_navigation_menu_handle.clone())
+            .menu({
+                let menu = self.agent_navigation_menu.clone();
+                move |window, cx| {
+                    telemetry::event!("View Thread History Clicked");
+
+                    if let Some(menu) = menu.as_ref() {
+                        menu.update(cx, |_, cx| {
+                            cx.defer_in(window, |menu, window, cx| {
+                                menu.rebuild(window, cx);
+                            });
+                        })
+                    }
+                    menu.clone()
+                }
+            })
+    }
+
     fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
         let focus_handle = self.focus_handle(cx);
 
@@ -4493,6 +4567,8 @@ impl AgentPanel {
             selected_agent.into_any_element()
         };
 
+        let show_history_menu = self.has_history_for_selected_agent(cx);
+        let agent_v2_enabled = agent_v2_enabled(cx);
         let is_empty_state = !self.active_thread_has_messages(cx);
 
         let is_in_history_or_config = self.is_history_or_configuration_visible();
@@ -4514,7 +4590,7 @@ impl AgentPanel {
                 }))
         };
 
-        let use_v2_empty_toolbar = is_empty_state && !is_in_history_or_config;
+        let use_v2_empty_toolbar = agent_v2_enabled && is_empty_state && !is_in_history_or_config;
 
         let max_content_width = AgentSettings::get_global(cx).max_content_width;
 
@@ -4584,11 +4660,17 @@ impl AgentPanel {
                         .pl(DynamicSpacing::Base04.rems(cx))
                         .child(agent_selector_menu)
                         .when(
-                            has_visible_worktrees && self.project_has_git_repository(cx),
+                            agent_v2_enabled
+                                && has_visible_worktrees
+                                && self.project_has_git_repository(cx),
                             |this| this.child(self.render_start_thread_in_selector(cx)),
                         )
                         .when(
-                            matches!(self.start_thread_in, StartThreadIn::NewWorktree { .. }),
+                            agent_v2_enabled
+                                && matches!(
+                                    self.start_thread_in,
+                                    StartThreadIn::NewWorktree { .. }
+                                ),
                             |this| this.child(self.render_new_worktree_branch_selector(cx)),
                         ),
                 )
@@ -4599,6 +4681,13 @@ impl AgentPanel {
                         .gap_1()
                         .pl_1()
                         .pr_1()
+                        .when(show_history_menu && !agent_v2_enabled, |this| {
+                            this.child(self.render_recent_entries_menu(
+                                IconName::MenuAltTemp,
+                                Corner::TopRight,
+                                cx,
+                            ))
+                        })
                         .child(full_screen_button)
                         .child(self.render_panel_options_menu(window, cx)),
                 )
@@ -4644,6 +4733,13 @@ impl AgentPanel {
                         .pl_1()
                         .pr_1()
                         .child(new_thread_menu)
+                        .when(show_history_menu && !agent_v2_enabled, |this| {
+                            this.child(self.render_recent_entries_menu(
+                                IconName::MenuAltTemp,
+                                Corner::TopRight,
+                                cx,
+                            ))
+                        })
                         .child(full_screen_button)
                         .child(self.render_panel_options_menu(window, cx)),
                 )

crates/agent_ui/src/agent_ui.rs 🔗

@@ -48,7 +48,7 @@ use agent_settings::{AgentProfileId, AgentSettings};
 use command_palette_hooks::CommandPaletteFilter;
 use feature_flags::FeatureFlagAppExt as _;
 use fs::Fs;
-use gpui::{Action, App, Context, Entity, SharedString, Window, actions};
+use gpui::{Action, App, Context, Entity, SharedString, UpdateGlobal as _, Window, actions};
 use language::{
     LanguageRegistry,
     language_settings::{AllLanguageSettings, EditPredictionProvider},
@@ -58,9 +58,10 @@ use language_model::{
 };
 use project::{AgentId, DisableAiSettings};
 use prompt_store::PromptBuilder;
+use release_channel::ReleaseChannel;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{LanguageModelSelection, Settings as _, SettingsStore};
+use settings::{DockPosition, DockSide, LanguageModelSelection, Settings as _, SettingsStore};
 use std::any::TypeId;
 use workspace::Workspace;
 
@@ -514,7 +515,34 @@ pub fn init(
     })
     .detach();
 
-    maybe_backfill_editor_layout(fs, is_new_install, cx);
+    let agent_v2_enabled = agent_v2_enabled(cx);
+    if agent_v2_enabled {
+        maybe_backfill_editor_layout(fs, is_new_install, cx);
+    }
+
+    SettingsStore::update_global(cx, |store, cx| {
+        store.update_default_settings(cx, |defaults| {
+            if agent_v2_enabled {
+                defaults.agent.get_or_insert_default().dock = Some(DockPosition::Left);
+                defaults.project_panel.get_or_insert_default().dock = Some(DockSide::Right);
+                defaults.outline_panel.get_or_insert_default().dock = Some(DockSide::Right);
+                defaults.collaboration_panel.get_or_insert_default().dock =
+                    Some(DockPosition::Right);
+                defaults.git_panel.get_or_insert_default().dock = Some(DockPosition::Right);
+            } else {
+                defaults.agent.get_or_insert_default().dock = Some(DockPosition::Right);
+                defaults.project_panel.get_or_insert_default().dock = Some(DockSide::Left);
+                defaults.outline_panel.get_or_insert_default().dock = Some(DockSide::Left);
+                defaults.collaboration_panel.get_or_insert_default().dock =
+                    Some(DockPosition::Left);
+                defaults.git_panel.get_or_insert_default().dock = Some(DockPosition::Left);
+            }
+        });
+    });
+}
+
+fn agent_v2_enabled(cx: &App) -> bool {
+    !matches!(ReleaseChannel::try_global(cx), Some(ReleaseChannel::Stable))
 }
 
 fn maybe_backfill_editor_layout(fs: Arc<dyn Fs>, is_new_install: bool, cx: &mut App) {
@@ -542,6 +570,7 @@ fn maybe_backfill_editor_layout(fs: Arc<dyn Fs>, is_new_install: bool, cx: &mut
 fn update_command_palette_filter(cx: &mut App) {
     let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
     let agent_enabled = AgentSettings::get_global(cx).enabled;
+    let agent_v2_enabled = agent_v2_enabled(cx);
 
     let edit_prediction_provider = AllLanguageSettings::get_global(cx)
         .edit_predictions
@@ -611,7 +640,11 @@ fn update_command_palette_filter(cx: &mut App) {
             filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
         }
 
-        filter.show_namespace("multi_workspace");
+        if agent_v2_enabled {
+            filter.show_namespace("multi_workspace");
+        } else {
+            filter.hide_namespace("multi_workspace");
+        }
     });
 }
 

crates/auto_update_ui/src/auto_update_ui.rs 🔗

@@ -196,59 +196,60 @@ impl Dismissable for ParallelAgentAnnouncement {
 }
 
 fn announcement_for_version(version: &Version, cx: &App) -> Option<AnnouncementContent> {
-    match (version.major, version.minor, version.patch) {
-        (0, _, _) => {
-            if ParallelAgentAnnouncement::dismissed(cx) {
-                None
-            } else {
-                let fs = <dyn Fs>::global(cx);
-                Some(AnnouncementContent {
-                    heading: "Introducing Parallel Agents".into(),
-                    description: "Run multiple agent threads simultaneously across projects."
-                        .into(),
-                    bullet_items: vec![
-                        "Use your favorite agents in parallel".into(),
-                        "Optionally isolate agents using worktrees".into(),
-                        "Combine multiple projects in one window".into(),
-                    ],
-                    primary_action_label: "Try Now".into(),
-                    primary_action_url: None,
-                    primary_action_callback: Some(Arc::new(move |window, cx| {
-                        let already_agent_layout =
-                            matches!(AgentSettings::get_layout(cx), WindowLayout::Agent(_));
-
-                        let update;
-                        if !already_agent_layout {
-                            update = Some(AgentSettings::set_layout(
-                                WindowLayout::Agent(None),
-                                fs.clone(),
-                                cx,
-                            ));
-                        } else {
-                            update = None;
-                        }
+    let version_with_parallel_agents = match ReleaseChannel::global(cx) {
+        ReleaseChannel::Stable => Version::new(0, 233, 0),
+        ReleaseChannel::Dev | ReleaseChannel::Nightly | ReleaseChannel::Preview => {
+            Version::new(0, 232, 0)
+        }
+    };
 
-                        window
-                            .spawn(cx, async move |cx| {
-                                if let Some(update) = update {
-                                    update.await.ok();
-                                }
+    if *version >= version_with_parallel_agents && !ParallelAgentAnnouncement::dismissed(cx) {
+        let fs = <dyn Fs>::global(cx);
+        Some(AnnouncementContent {
+            heading: "Introducing Parallel Agents".into(),
+            description: "Run multiple agent threads simultaneously across projects.".into(),
+            bullet_items: vec![
+                "Use your favorite agents in parallel".into(),
+                "Optionally isolate agents using worktrees".into(),
+                "Combine multiple projects in one window".into(),
+            ],
+            primary_action_label: "Try Now".into(),
+            primary_action_url: None,
+            primary_action_callback: Some(Arc::new(move |window, cx| {
+                let already_agent_layout =
+                    matches!(AgentSettings::get_layout(cx), WindowLayout::Agent(_));
+
+                let update;
+                if !already_agent_layout {
+                    update = Some(AgentSettings::set_layout(
+                        WindowLayout::Agent(None),
+                        fs.clone(),
+                        cx,
+                    ));
+                } else {
+                    update = None;
+                }
 
-                                cx.update(|window, cx| {
-                                    window.dispatch_action(Box::new(FocusWorkspaceSidebar), cx);
-                                    window.dispatch_action(Box::new(FocusAgent), cx);
-                                })
-                            })
-                            .detach();
-                    })),
-                    on_dismiss: Some(Arc::new(|cx| {
-                        ParallelAgentAnnouncement::set_dismissed(true, cx)
-                    })),
-                    secondary_action_url: Some("https://zed.dev/blog/".into()),
-                })
-            }
-        }
-        _ => None,
+                window
+                    .spawn(cx, async move |cx| {
+                        if let Some(update) = update {
+                            update.await.ok();
+                        }
+
+                        cx.update(|window, cx| {
+                            window.dispatch_action(Box::new(FocusWorkspaceSidebar), cx);
+                            window.dispatch_action(Box::new(FocusAgent), cx);
+                        })
+                    })
+                    .detach();
+            })),
+            on_dismiss: Some(Arc::new(|cx| {
+                ParallelAgentAnnouncement::set_dismissed(true, cx)
+            })),
+            secondary_action_url: Some("https://zed.dev/blog/".into()),
+        })
+    } else {
+        None
     }
 }
 

crates/feature_flags/src/flags.rs 🔗

@@ -12,6 +12,16 @@ impl FeatureFlag for PanicFeatureFlag {
     const NAME: &'static str = "panic";
 }
 
+pub struct AgentV2FeatureFlag;
+
+impl FeatureFlag for AgentV2FeatureFlag {
+    const NAME: &'static str = "agent-v2";
+
+    fn enabled_for_staff() -> bool {
+        true
+    }
+}
+
 /// A feature flag for granting access to beta ACP features.
 ///
 /// We reuse this feature flag for new betas, so don't delete it if it is not currently in use.

crates/settings_ui/src/page_data.rs 🔗

@@ -1,5 +1,6 @@
 use gpui::{Action as _, App};
 use itertools::Itertools as _;
+use release_channel::ReleaseChannel;
 use settings::{
     AudioInputDeviceName, AudioOutputDeviceName, LanguageSettingsContent, SemanticTokens,
     SettingsContent,
@@ -7281,7 +7282,7 @@ fn ai_page(cx: &App) -> SettingsPage {
         ]
     }
 
-    fn agent_configuration_section(_cx: &App) -> Box<[SettingsPageItem]> {
+    fn agent_configuration_section(cx: &App) -> Box<[SettingsPageItem]> {
         let mut items = vec![
             SettingsPageItem::SectionHeader("Agent Configuration"),
             SettingsPageItem::SubPageLink(SubPageLink {
@@ -7295,28 +7296,30 @@ fn ai_page(cx: &App) -> SettingsPage {
             }),
         ];
 
-        items.push(SettingsPageItem::SettingItem(SettingItem {
-            title: "New Thread Location",
-            description: "Whether to start a new thread in the current local project or in a new Git worktree.",
-            field: Box::new(SettingField {
-                json_path: Some("agent.new_thread_location"),
-                pick: |settings_content| {
-                    settings_content
-                        .agent
-                        .as_ref()?
-                        .new_thread_location
-                        .as_ref()
-                },
-                write: |settings_content, value| {
-                    settings_content
-                        .agent
-                        .get_or_insert_default()
-                        .new_thread_location = value;
-                },
-            }),
-            metadata: None,
-            files: USER,
-        }));
+        if !matches!(ReleaseChannel::try_global(cx), Some(ReleaseChannel::Stable)) {
+            items.push(SettingsPageItem::SettingItem(SettingItem {
+                title: "New Thread Location",
+                description: "Whether to start a new thread in the current local project or in a new Git worktree.",
+                field: Box::new(SettingField {
+                    json_path: Some("agent.new_thread_location"),
+                    pick: |settings_content| {
+                        settings_content
+                            .agent
+                            .as_ref()?
+                            .new_thread_location
+                            .as_ref()
+                    },
+                    write: |settings_content, value| {
+                        settings_content
+                            .agent
+                            .get_or_insert_default()
+                            .new_thread_location = value;
+                    },
+                }),
+                metadata: None,
+                files: USER,
+            }));
+        }
 
         items.extend([
             SettingsPageItem::SettingItem(SettingItem {

crates/workspace/Cargo.toml 🔗

@@ -36,7 +36,6 @@ clock.workspace = true
 collections.workspace = true
 component.workspace = true
 db.workspace = true
-feature_flags.workspace = true
 fs.workspace = true
 futures.workspace = true
 git.workspace = true
@@ -51,6 +50,7 @@ node_runtime.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
 project.workspace = true
+release_channel.workspace = true
 remote.workspace = true
 schemars.workspace = true
 serde.workspace = true

crates/workspace/src/multi_workspace.rs 🔗

@@ -7,6 +7,7 @@ use gpui::{
 };
 pub use project::ProjectGroupKey;
 use project::{DirectoryLister, DisableAiSettings, Project};
+use release_channel::ReleaseChannel;
 use remote::RemoteConnectionOptions;
 use settings::Settings;
 pub use settings::SidebarSide;
@@ -397,7 +398,8 @@ impl MultiWorkspace {
     }
 
     pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
-        !DisableAiSettings::get_global(cx).disable_ai
+        !matches!(ReleaseChannel::try_global(cx), Some(ReleaseChannel::Stable))
+            && !DisableAiSettings::get_global(cx).disable_ai
     }
 
     pub fn toggle_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {

crates/workspace/src/persistence.rs 🔗

@@ -2507,7 +2507,7 @@ mod tests {
             read_multi_workspace_state,
         },
     };
-    use feature_flags::FeatureFlagAppExt;
+
     use gpui::AppContext as _;
     use pretty_assertions::assert_eq;
     use project::Project;
@@ -2527,10 +2527,6 @@ mod tests {
     async fn test_multi_workspace_serializes_on_add_and_remove(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let project1 = Project::test(fs.clone(), [], cx).await;
         let project2 = Project::test(fs.clone(), [], cx).await;
@@ -4088,10 +4084,6 @@ mod tests {
     async fn test_flush_serialization_completes_before_quit(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let project = Project::test(fs.clone(), [], cx).await;
 
@@ -4132,10 +4124,6 @@ mod tests {
     async fn test_create_workspace_serialization(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let project = Project::test(fs.clone(), [], cx).await;
 
@@ -4188,10 +4176,6 @@ mod tests {
     async fn test_remove_workspace_clears_session_binding(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let dir = unique_test_dir(&fs, "remove").await;
         let project1 = Project::test(fs.clone(), [], cx).await;
@@ -4279,10 +4263,6 @@ mod tests {
     async fn test_remove_workspace_not_restored_as_zombie(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let dir1 = tempfile::TempDir::with_prefix("zombie_test1").unwrap();
         let dir2 = tempfile::TempDir::with_prefix("zombie_test2").unwrap();
@@ -4385,10 +4365,6 @@ mod tests {
     async fn test_pending_removal_tasks_drained_on_flush(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let dir = unique_test_dir(&fs, "pending-removal").await;
         let project1 = Project::test(fs.clone(), [], cx).await;
@@ -4489,10 +4465,6 @@ mod tests {
     async fn test_create_workspace_bounds_observer_uses_fresh_id(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let project = Project::test(fs.clone(), [], cx).await;
 
@@ -4545,10 +4517,6 @@ mod tests {
     async fn test_flush_serialization_writes_bounds(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
         let dir = tempfile::TempDir::with_prefix("flush_bounds_test").unwrap();
         fs.insert_tree(dir.path(), json!({})).await;
@@ -4704,10 +4672,6 @@ mod tests {
     ) {
         crate::tests::init_test(cx);
 
-        cx.update(|cx| {
-            cx.set_staff(true);
-        });
-
         let fs = fs::FakeFs::new(cx.executor());
 
         // Main git repo at /repo
@@ -4887,10 +4851,6 @@ mod tests {
     #[gpui::test]
     async fn test_remove_project_group_falls_back_to_neighbor(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
-        cx.update(|cx| {
-            cx.set_staff(true);
-            cx.update_flags(true, vec!["agent-v2".to_string()]);
-        });
 
         let fs = fs::FakeFs::new(cx.executor());
         let dir_a = unique_test_dir(&fs, "group-a").await;
@@ -5002,10 +4962,6 @@ mod tests {
     #[gpui::test]
     async fn test_remove_fallback_skips_excluded_workspaces(cx: &mut gpui::TestAppContext) {
         crate::tests::init_test(cx);
-        cx.update(|cx| {
-            cx.set_staff(true);
-            cx.update_flags(true, vec!["agent-v2".to_string()]);
-        });
 
         let fs = fs::FakeFs::new(cx.executor());
         let dir = unique_test_dir(&fs, "shared").await;