π File detail
utils/plugins/orphanedPluginFilter.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes getGlobExclusionsForPluginCache and clearPluginCacheExclusions β mainly functions, hooks, or classes. Dependencies touch Node path helpers. It composes internal code from ripgrep and pluginDirectories (relative imports). What the file header says: Provides ripgrep glob exclusion patterns for orphaned plugin versions. When plugin versions are updated, old versions are marked with a `.orphaned_at` file but kept on disk for 7 days (since concurrent sessions might still reference them). During this window, Grep/Glob could retu.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
Provides ripgrep glob exclusion patterns for orphaned plugin versions. When plugin versions are updated, old versions are marked with a `.orphaned_at` file but kept on disk for 7 days (since concurrent sessions might still reference them). During this window, Grep/Glob could return files from orphaned versions, causing Claude to use outdated plugin code. We find `.orphaned_at` markers via a single ripgrep call and generate `--glob '!<dir>/**'` patterns for their parent directories. The cache is warmed in main.tsx AFTER cleanupOrphanedPluginVersionsInBackground settles disk state. Once populated, the exclusion list is frozen for the session unless /reload-plugins is called; subsequent disk mutations (autoupdate, concurrent sessions) don't affect it.
π€ Exports (heuristic)
getGlobExclusionsForPluginCacheclearPluginCacheExclusions
π External import roots
Package roots from from "β¦" (relative paths omitted).
path
π₯οΈ Source preview
/**
* Provides ripgrep glob exclusion patterns for orphaned plugin versions.
*
* When plugin versions are updated, old versions are marked with a
* `.orphaned_at` file but kept on disk for 7 days (since concurrent
* sessions might still reference them). During this window, Grep/Glob
* could return files from orphaned versions, causing Claude to use
* outdated plugin code.
*
* We find `.orphaned_at` markers via a single ripgrep call and generate
* `--glob '!<dir>/**'` patterns for their parent directories. The cache
* is warmed in main.tsx AFTER cleanupOrphanedPluginVersionsInBackground
* settles disk state. Once populated, the exclusion list is frozen for
* the session unless /reload-plugins is called; subsequent disk mutations
* (autoupdate, concurrent sessions) don't affect it.
*/
import { dirname, isAbsolute, join, normalize, relative, sep } from 'path'
import { ripGrep } from '../ripgrep.js'
import { getPluginsDirectory } from './pluginDirectories.js'
// Inlined from cacheUtils.ts to avoid a circular dep through commands.js.
const ORPHANED_AT_FILENAME = '.orphaned_at'
/** Session-scoped cache. Frozen once computed β only cleared by explicit /reload-plugins. */
let cachedExclusions: string[] | null = null
/**
* Get ripgrep glob exclusion patterns for orphaned plugin versions.
*
* @param searchPath - When provided, exclusions are only returned if the
* search overlaps the plugin cache directory (avoids unnecessary --glob
* args for searches outside the cache).
*
* Warmed eagerly in main.tsx after orphan GC; the lazy-compute path here
* is a fallback. Best-effort: returns empty array if anything goes wrong.
*/
export async function getGlobExclusionsForPluginCache(
searchPath?: string,
): Promise<string[]> {
const cachePath = normalize(join(getPluginsDirectory(), 'cache'))
if (searchPath && !pathsOverlap(searchPath, cachePath)) {
return []
}
if (cachedExclusions !== null) {
return cachedExclusions
}
try {
// Find all .orphaned_at files within the plugin cache directory.
// --hidden: marker is a dotfile. --no-ignore: don't let a stray
// .gitignore hide it. --max-depth 4: marker is always at
// cache/<marketplace>/<plugin>/<version>/.orphaned_at β don't recurse
// into plugin contents (node_modules, etc.). Never-aborts signal: no
// caller signal to thread.
const markers = await ripGrep(
[
'--files',
'--hidden',
'--no-ignore',
'--max-depth',
'4',
'--glob',
ORPHANED_AT_FILENAME,
],
cachePath,
new AbortController().signal,
)
cachedExclusions = markers.map(markerPath => {
// ripgrep may return absolute or relative β normalize to relative.
const versionDir = dirname(markerPath)
const rel = isAbsolute(versionDir)
? relative(cachePath, versionDir)
: versionDir
// ripgrep glob patterns always use forward slashes, even on Windows
const posixRelative = rel.replace(/\\/g, '/')
return `!**/${posixRelative}/**`
})
return cachedExclusions
} catch {
// Best-effort β don't break core search tools if ripgrep fails here
cachedExclusions = []
return cachedExclusions
}
}
export function clearPluginCacheExclusions(): void {
cachedExclusions = null
}
/**
* One path is a prefix of the other. Special-cases root (normalize('/') + sep
* = '//'). Case-insensitive on win32 since normalize() doesn't lowercase
* drive letters and CLAUDE_CODE_PLUGIN_CACHE_DIR may disagree with resolved.
*/
function pathsOverlap(a: string, b: string): boolean {
const na = normalizeForCompare(a)
const nb = normalizeForCompare(b)
return (
na === nb ||
na === sep ||
nb === sep ||
na.startsWith(nb + sep) ||
nb.startsWith(na + sep)
)
}
function normalizeForCompare(p: string): string {
const n = normalize(p)
return process.platform === 'win32' ? n.toLowerCase() : n
}