Fix rules files not loading and config file rescan clearing tokens (#53659)

Anders Jenbo created

Fixes #52453
Fixes #53246

Both issues were introduced by #51208 ("Handle Linux FS Rescan Events"),
which added `PathEventKind::Rescan` handling.

## Rules files not loading (#52453)

`load_worktree_info_for_system_prompt` called `load_worktree_rules_file`
synchronously, which uses `entry_for_path()` on the current worktree
snapshot. If the background scanner hasn't finished its initial scan,
the entry doesn't exist yet and the lookup returns `None` — the code
concludes no rules file exists. This was always a latent race condition,
but became more visible after Rescan events were introduced, since they
can trigger additional `WorktreeUpdatedEntries` churn that interacts
with the refresh mechanism.

The fix awaits `scan_complete()` on local worktrees before performing
the rules file lookup, ensuring the full directory tree is indexed
first.

## Config file rescan clearing OAuth tokens (#53246)

The `Rescan` handlers in `watch_config_dir` used
`fs.load(file_path).await.unwrap_or_default()`, which turns any
file-read error into an empty string. This empty string flows to
consumers like `CopilotChat`, where `extract_oauth_token("")` returns
`None`, causing the OAuth token to be unconditionally overwritten with
`None` — triggering re-authentication.

The fix changes both Rescan handlers to skip files that fail to load
(using `if let Ok(contents) = ...`), matching the pattern already used
by the `Created`/`Changed` handler.

Release Notes:

- Fixed rules files (AGENTS.md, CLAUDE.md, .rules, etc.) sometimes not
being applied in agent threads.
- Fixed GitHub Copilot re-prompting for authentication after filesystem
rescan events.

Change summary

crates/agent/src/agent.rs            | 31 +++++++++++++++++------------
crates/settings/src/settings_file.rs | 10 +++++---
2 files changed, 24 insertions(+), 17 deletions(-)

Detailed changes

crates/agent/src/agent.rs 🔗

@@ -591,6 +591,7 @@ impl NativeAgent {
         let tree = worktree.read(cx);
         let root_name = tree.root_name_str().into();
         let abs_path = tree.abs_path();
+        let scan_complete = tree.as_local().map(|local| local.scan_complete());
 
         let mut context = WorktreeContext {
             root_name,
@@ -598,20 +599,24 @@ impl NativeAgent {
             rules_file: None,
         };
 
-        let rules_task = Self::load_worktree_rules_file(worktree, project, cx);
-        let Some(rules_task) = rules_task else {
-            return Task::ready((context, None));
-        };
+        cx.spawn(async move |cx| {
+            if let Some(scan_complete) = scan_complete {
+                scan_complete.await;
+            }
 
-        cx.spawn(async move |_| {
-            let (rules_file, rules_file_error) = match rules_task.await {
-                Ok(rules_file) => (Some(rules_file), None),
-                Err(err) => (
-                    None,
-                    Some(RulesLoadingError {
-                        message: format!("{err}").into(),
-                    }),
-                ),
+            let rules_task = cx.update(|cx| Self::load_worktree_rules_file(worktree, project, cx));
+
+            let (rules_file, rules_file_error) = match rules_task {
+                Some(rules_task) => match rules_task.await {
+                    Ok(rules_file) => (Some(rules_file), None),
+                    Err(err) => (
+                        None,
+                        Some(RulesLoadingError {
+                            message: format!("{err}").into(),
+                        }),
+                    ),
+                },
+                None => (None, None),
             };
             context.rules_file = rules_file;
             (context, rules_file_error)

crates/settings/src/settings_file.rs 🔗

@@ -232,8 +232,9 @@ pub fn watch_config_dir(
                             }
                             Some(PathEventKind::Rescan) => {
                                 for file_path in &config_paths {
-                                    let contents = fs.load(file_path).await.unwrap_or_default();
-                                    if tx.unbounded_send(contents).is_err() {
+                                    if let Ok(contents) = fs.load(file_path).await
+                                        && tx.unbounded_send(contents).is_err()
+                                    {
                                         return;
                                     }
                                 }
@@ -244,8 +245,9 @@ pub fn watch_config_dir(
                         && event.path == dir_path
                     {
                         for file_path in &config_paths {
-                            let contents = fs.load(file_path).await.unwrap_or_default();
-                            if tx.unbounded_send(contents).is_err() {
+                            if let Ok(contents) = fs.load(file_path).await
+                                && tx.unbounded_send(contents).is_err()
+                            {
                                 return;
                             }
                         }