Ignore generated system skills in the skills watcher (#35408)

## What changed

- Exclude `SkillScope::System` roots from watcher registration because generated
  system skills are installed before the watcher starts.
- Ignore events under the system skill cache that arrive through the recursively
  watched legacy user-skills root, avoiding unnecessary cache clears and
  `SkillsChanged` notifications.

GitOrigin-RevId: ce8ba28ba873372b638950b676e4d09172a5c688
This commit is contained in:
victor-openai
2026-07-25 23:49:05 +00:00
committed by copyberry
parent 20dafe201d
commit 62fd410384
2 changed files with 29 additions and 5 deletions

View File

@@ -309,7 +309,11 @@ impl MessageProcessor {
thread_manager
.plugins_manager()
.set_analytics_events_client(analytics_events_client.clone());
let skills_watcher = SkillsWatcher::new(thread_manager.skills_service(), outgoing.clone());
let skills_watcher = SkillsWatcher::new(
thread_manager.skills_service(),
&config.codex_home,
outgoing.clone(),
);
let pending_thread_unloads = Arc::new(Mutex::new(HashSet::new()));
let thread_watch_manager =

View File

@@ -15,7 +15,9 @@ use codex_file_watcher::Receiver;
use codex_file_watcher::ThrottledWatchReceiver;
use codex_file_watcher::WatchPath;
use codex_file_watcher::WatchRegistration;
use codex_protocol::protocol::SkillScope;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_skills::system_cache_root_dir;
use codex_utils_absolute_path::AbsolutePathBuf;
use tokio_util::sync::CancellationToken;
use tokio_util::sync::DropGuard;
@@ -36,6 +38,7 @@ pub(crate) struct SkillsWatcher {
impl SkillsWatcher {
pub(crate) fn new(
skills_service: Arc<SkillsService>,
codex_home: &AbsolutePathBuf,
outgoing: Arc<OutgoingMessageSender>,
) -> Arc<Self> {
let file_watcher = match FileWatcher::new() {
@@ -48,7 +51,14 @@ impl SkillsWatcher {
let (subscriber, rx) = file_watcher.add_subscriber();
let shutdown_token = CancellationToken::new();
let shutdown_drop_guard = shutdown_token.clone().drop_guard();
Self::spawn_event_loop(rx, skills_service, outgoing, shutdown_token.child_token());
let system_skills_root = system_cache_root_dir(codex_home);
Self::spawn_event_loop(
rx,
skills_service,
system_skills_root,
outgoing,
shutdown_token.child_token(),
);
Arc::new(Self {
subscriber,
runtime_extra_roots_registration: Mutex::new(WatchRegistration::default()),
@@ -114,8 +124,9 @@ impl SkillsWatcher {
.skill_roots_for_config(&skills_input, Some(environment.get_filesystem()))
.await
.into_iter()
// Plugin roots are invalidated by plugin lifecycle operations.
.filter(|root| root.plugin_identity.is_none())
// Plugin roots have explicit lifecycle invalidation; generated system skills are
// installed before this watcher starts.
.filter(|root| root.plugin_identity.is_none() && root.scope != SkillScope::System)
.map(|root| WatchPath {
path: root.path.into_path_buf(),
recursive: true,
@@ -127,6 +138,7 @@ impl SkillsWatcher {
fn spawn_event_loop(
rx: Receiver,
skills_service: Arc<SkillsService>,
system_skills_root: AbsolutePathBuf,
outgoing: Arc<OutgoingMessageSender>,
shutdown_token: CancellationToken,
) {
@@ -141,8 +153,16 @@ impl SkillsWatcher {
_ = shutdown_token.cancelled() => break,
event = rx.recv() => event,
};
if event.is_none() {
let Some(event) = event else {
break;
};
// The legacy user-skills root contains `.system` and is watched recursively.
if event
.paths
.iter()
.all(|path| path.starts_with(system_skills_root.as_path()))
{
continue;
}
skills_service.clear_cache();
outgoing