pluginBlocklist.ts
utils/plugins/pluginBlocklist.ts
No strong subsystem tag
128
Lines
4361
Bytes
2
Exports
7
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with general runtime concerns. It contains 128 lines, 7 detected imports, and 2 detected exports.
Important relationships
Detected exports
detectDelistedPluginsdetectAndUninstallDelistedPlugins
Keywords
marketplacepluginspluginiddelistedscopemarketplacenameinstalledpluginscontinuepluginsuffix
Detected imports
../../services/plugins/pluginOperations.js../debug.js../errors.js./installedPluginsManager.js./marketplaceManager.js./pluginFlagging.js./schemas.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
/**
* Plugin delisting detection.
*
* Compares installed plugins against marketplace manifests to find plugins
* that have been removed, and auto-uninstalls them.
*
* The security.json fetch was removed (see #25447) — ~29.5M/week GitHub hits
* for UI reason/text only. If re-introduced, serve from downloads.claude.ai.
*/
import { uninstallPluginOp } from '../../services/plugins/pluginOperations.js'
import { logForDebugging } from '../debug.js'
import { errorMessage } from '../errors.js'
import { loadInstalledPluginsV2 } from './installedPluginsManager.js'
import {
getMarketplace,
loadKnownMarketplacesConfigSafe,
} from './marketplaceManager.js'
import {
addFlaggedPlugin,
getFlaggedPlugins,
loadFlaggedPlugins,
} from './pluginFlagging.js'
import type { InstalledPluginsFileV2, PluginMarketplace } from './schemas.js'
/**
* Detect plugins installed from a marketplace that are no longer listed there.
*
* @param installedPlugins All installed plugins
* @param marketplace The marketplace to check against
* @param marketplaceName The marketplace name suffix (e.g. "claude-plugins-official")
* @returns List of delisted plugin IDs in "name@marketplace" format
*/
export function detectDelistedPlugins(
installedPlugins: InstalledPluginsFileV2,
marketplace: PluginMarketplace,
marketplaceName: string,
): string[] {
const marketplacePluginNames = new Set(marketplace.plugins.map(p => p.name))
const suffix = `@${marketplaceName}`
const delisted: string[] = []
for (const pluginId of Object.keys(installedPlugins.plugins)) {
if (!pluginId.endsWith(suffix)) continue
const pluginName = pluginId.slice(0, -suffix.length)
if (!marketplacePluginNames.has(pluginName)) {
delisted.push(pluginId)
}
}
return delisted
}
/**
* Detect delisted plugins across all marketplaces, auto-uninstall them,
* and record them as flagged.
*
* This is the core delisting enforcement logic, shared between interactive
* mode (useManagePlugins) and headless mode (main.tsx print path).
*
* @returns List of newly flagged plugin IDs
*/
export async function detectAndUninstallDelistedPlugins(): Promise<string[]> {
await loadFlaggedPlugins()
const installedPlugins = loadInstalledPluginsV2()
const alreadyFlagged = getFlaggedPlugins()
// Read-only iteration — Safe variant so a corrupted config doesn't throw
// out of this function (it's called in the same try-block as loadAllPlugins
// in useManagePlugins, so a throw here would void loadAllPlugins' resilience).
const knownMarketplaces = await loadKnownMarketplacesConfigSafe()
const newlyFlagged: string[] = []
for (const marketplaceName of Object.keys(knownMarketplaces)) {
try {
const marketplace = await getMarketplace(marketplaceName)
if (!marketplace.forceRemoveDeletedPlugins) continue
const delisted = detectDelistedPlugins(
installedPlugins,
marketplace,
marketplaceName,
)
for (const pluginId of delisted) {
if (pluginId in alreadyFlagged) continue
// Skip managed-only plugins — enterprise admin should handle those
const installations = installedPlugins.plugins[pluginId] ?? []
const hasUserInstall = installations.some(
i =>
i.scope === 'user' || i.scope === 'project' || i.scope === 'local',
)
if (!hasUserInstall) continue
// Auto-uninstall the delisted plugin from all user-controllable scopes
for (const installation of installations) {
const { scope } = installation
if (scope !== 'user' && scope !== 'project' && scope !== 'local') {
continue
}
try {
await uninstallPluginOp(pluginId, scope)
} catch (error) {
logForDebugging(
`Failed to auto-uninstall delisted plugin ${pluginId} from ${scope}: ${errorMessage(error)}`,
{ level: 'error' },
)
}
}
await addFlaggedPlugin(pluginId)
newlyFlagged.push(pluginId)
}
} catch (error) {
// Marketplace may not be available yet — log and continue
logForDebugging(
`Failed to check for delisted plugins in "${marketplaceName}": ${errorMessage(error)}`,
{ level: 'warn' },
)
}
}
return newlyFlagged
}