Refresh plugin skills after out-of-process version changes (#42284)

## Why

Another process sharing `CODEX_HOME` can replace an installed plugin version
without invalidating the current process's caches. This could leave skill
listings tied to stale plugin paths and retain obsolete plugin generations.

## What changed

- Reject cached plugin loads when their roots no longer match the active
  installation, so skills are reloaded after upgrades or rollbacks.
- Keep the 32 most recently used configuration-based skill snapshots while
  allowing callers to continue using snapshots that have been evicted.

## Testing

- Cover external plugin upgrades and rollbacks through `skills/list`, including
  a subsequent warm-cache read.
- Cover cache eviction, reuse, and the lifetime of caller-held snapshots.

GitOrigin-RevId: ca00f9539c01461e3945d340bf63f8436665220b
This commit is contained in:
jif
2026-09-02 14:14:44 +00:00
committed by copyberry
parent 8d32abcd01
commit 50fffd5ed3
4 changed files with 218 additions and 37 deletions
+20 -3
View File
@@ -564,9 +564,26 @@ struct LoadedPluginsCache {
}
impl LoadedPluginsCache {
fn get(&mut self, key: &PluginLoadCacheKey) -> Option<&LoadedPluginsCacheEntry> {
fn get(
&mut self,
key: &PluginLoadCacheKey,
store: &PluginStore,
) -> Option<&LoadedPluginsCacheEntry> {
let index = self.entries.iter().position(|entry| &entry.key == key)?;
let entry = self.entries.remove(index)?;
// Another process can replace installed versions without invalidating this manager.
// Keep the parsed skills paired with the installation whose paths they advertise.
if entry.plugins.iter().any(|plugin| {
let Ok(plugin_id) = PluginId::parse(&plugin.config_name) else {
return false;
};
let installed_root = store
.active_plugin_root(&plugin_id)
.unwrap_or_else(|| store.plugin_base_root(&plugin_id));
installed_root != plugin.root
}) {
return None;
}
self.entries.push_front(entry);
self.entries.front()
}
@@ -745,7 +762,7 @@ impl PluginsManager {
self.loaded_plugins_cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(&key)
.get(&key, &self.store)
.map(|cached| cached.plugin_skill_snapshots.clone())
}
@@ -968,7 +985,7 @@ impl PluginsManager {
self.loaded_plugins_cache
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(key)
.get(key, &self.store)
.map(|cached| cached.plugins.clone())
}