From f7ab90721616d7c6ee84fa9bf10cdf925e8e9b1f Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 31 Mar 2026 08:37:20 -0700 Subject: [PATCH] Fix agent servers loading environment from home dir instead of project dir (#52763) All local agent server types were calling `local_directory_environment` with `paths::home_dir()`, causing direnv and shell environment to be loaded from `~` rather than the project's worktree directory. This meant project-specific `.envrc` variables (e.g. Google Vertex credentials) were never picked up by external agents like Claude. Added `default_environment()` on `ProjectEnvironment` that resolves the default visible worktree path and uses it for environment loading, falling back to home_dir() only when no worktree is available. 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 default environment variable context for external agents --- crates/project/src/agent_server_store.rs | 25 ++++-------------------- crates/project/src/environment.rs | 21 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index 2b7a0c4e1c7189f5ba3643a8d853dda6ed03537b..0b6bb2b739f677ca1f4f3d5558538372ec6e86ff 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -22,7 +22,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{RegisterSetting, SettingsStore}; use sha2::{Digest, Sha256}; -use task::Shell; use url::Url; use util::{ResultExt as _, debug_panic}; @@ -1183,11 +1182,7 @@ impl ExternalAgentServer for LocalExtensionArchiveAgent { // Get project environment let mut env = project_environment .update(cx, |project_environment, cx| { - project_environment.local_directory_environment( - &Shell::System, - paths::home_dir().as_path().into(), - cx, - ) + project_environment.default_environment(cx) })? .await .unwrap_or_default(); @@ -1377,11 +1372,7 @@ impl ExternalAgentServer for LocalRegistryArchiveAgent { cx.spawn(async move |cx| { let mut env = project_environment .update(cx, |project_environment, cx| { - project_environment.local_directory_environment( - &Shell::System, - paths::home_dir().as_path().into(), - cx, - ) + project_environment.default_environment(cx) })? .await .unwrap_or_default(); @@ -1555,11 +1546,7 @@ impl ExternalAgentServer for LocalRegistryNpxAgent { cx.spawn(async move |cx| { let mut env = project_environment .update(cx, |project_environment, cx| { - project_environment.local_directory_environment( - &Shell::System, - paths::home_dir().as_path().into(), - cx, - ) + project_environment.default_environment(cx) })? .await .unwrap_or_default(); @@ -1615,11 +1602,7 @@ impl ExternalAgentServer for LocalCustomAgent { cx.spawn(async move |cx| { let mut env = project_environment .update(cx, |project_environment, cx| { - project_environment.local_directory_environment( - &Shell::System, - paths::home_dir().as_path().into(), - cx, - ) + project_environment.default_environment(cx) })? .await .unwrap_or_default(); diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index 6a7f0311d04f14941b21ee9e32bda0faec2783b5..8156e172b91796ec3a9ef9446188a14bd537887e 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -194,6 +194,27 @@ impl ProjectEnvironment { .unwrap_or_else(|| Task::ready(None).shared()) } + /// Returns the project environment using the default worktree path. + /// This ensures that project-specific environment variables (e.g. from `.envrc`) + /// are loaded from the project directory rather than the home directory. + pub fn default_environment( + &mut self, + cx: &mut App, + ) -> Shared>>> { + let abs_path = self + .worktree_store + .read_with(cx, |worktree_store, cx| { + crate::Project::default_visible_worktree_paths(worktree_store, cx) + .into_iter() + .next() + }) + .ok() + .flatten() + .map(|path| Arc::::from(path)) + .unwrap_or_else(|| paths::home_dir().as_path().into()); + self.local_directory_environment(&Shell::System, abs_path, cx) + } + /// Returns the project environment, if possible. /// If the project was opened from the CLI, then the inherited CLI environment is returned. /// If it wasn't opened from the CLI, and an absolute path is given, then a shell is spawned in