{"record":{"id":"f7d2bb749aad3e48","repo":"ruvnet/ruflo","slug":"invalid-plugin","errorCode":"INVALID_PLUGIN","errorMessage":"Plugin '${pluginName}' not found","messagePattern":"Plugin '(.+?)' not found","errorType":"exception","errorClass":"PluginError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/shared/src/plugin-loader.ts","lineNumber":185,"sourceCode":"    } finally {\n      results.totalDuration = Date.now() - startTime;\n    }\n\n    // Start health checks if enabled\n    if (this.config.enableHealthChecks) {\n      this.startHealthChecks();\n    }\n\n    return results;\n  }\n\n  /**\n   * Unload a single plugin\n   */\n  async unloadPlugin(pluginName: string): Promise<void> {\n    const pluginInfo = this.registry.getPlugin(pluginName);\n    if (!pluginInfo) {\n      throw new PluginError(\n        `Plugin '${pluginName}' not found`,\n        pluginName,\n        'INVALID_PLUGIN'\n      );\n    }\n\n    // Check for dependents\n    const dependents = this.findDependents(pluginName);\n    if (dependents.length > 0) {\n      throw new PluginError(\n        `Cannot unload plugin '${pluginName}': depended on by ${dependents.join(', ')}`,\n        pluginName,\n        'DEPENDENCY_NOT_FOUND'\n      );\n    }\n\n    // Shutdown plugin\n    await this.shutdownPlugin(pluginInfo.plugin);","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/shared/src/plugin-loader.ts#L167-L203","documentation":"PluginLoader.unloadPlugin(pluginName) looks the name up in the plugin registry and throws PluginError with code 'INVALID_PLUGIN' when nothing is registered under that exact name. Note the code is misleading (the condition is 'not found', not 'invalid') - branch on the message or pluginName, not just the code. Names are case-sensitive; loaded names are visible via loader.getInitializationOrder().","triggerScenarios":"unloadPlugin('auth') when the plugin registered as 'AuthPlugin'; unloading before loadPlugins resolved; a typo in a config-driven unload list; calling unload twice where the first already removed it.","commonSituations":"Config lists using stale names after a plugin rename; case mismatches; hot-unload flows racing the initial load; teardown code written against docs instead of the actual registered names.","solutions":["Use the exact registered name - list them first via loader.getInitializationOrder()","Guard with a presence check before unloading","In unload-then-reload flows, ensure the original load completed before unloading"],"exampleFix":"// before\nawait loader.unloadPlugin('auth'); // actual name is 'auth-plugin'\n\n// after\nconst loaded = loader.getInitializationOrder();\nif (loaded.includes('auth-plugin')) {\n  await loader.unloadPlugin('auth-plugin');\n}","handlingStrategy":"validation","validationCode":"const loadedNames = loader.getInitializationOrder();\nif (!loadedNames.includes(pluginName)) {\n  throw new Error(`plugin '${pluginName}' is not loaded; loaded: ${loadedNames.join(', ')}`);\n}\nawait loader.unloadPlugin(pluginName);","typeGuard":null,"tryCatchPattern":"try {\n  await loader.unloadPlugin(name);\n} catch (e) {\n  if (e instanceof PluginError && e.code === 'INVALID_PLUGIN' && /not found/.test(e.message)) {\n    return; // already gone - idempotent unload\n  }\n  throw e;\n}","preventionTips":["Source unload names from loader.getInitializationOrder(), never from stale config","Names are case-sensitive: normalize or copy exactly","Remember the code on this error is 'INVALID_PLUGIN' even though the cause is not-found"],"tags":["plugins","loader","unload","not-found","naming"],"backgroundTag":"plugin-not-found","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}