{"record":{"id":"fd168626167d7bc5","repo":"ruvnet/ruflo","slug":"cannot-remove-name-required-by-dependents-jo","errorCode":null,"errorMessage":"Cannot remove ${name}: required by ${dependents.join(', ')}","messagePattern":"Cannot remove (.+?): required by (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts","lineNumber":370,"sourceCode":"    }\n\n    // Check dependents\n    const dependents = this.dependencyGraph.getDependents(name);\n\n    if (dependents.length > 0) {\n      if (options?.cascade) {\n        // Unload dependents first (in reverse order)\n        const order = this.dependencyGraph.getRemovalOrder(name);\n        for (const dep of order) {\n          if (dep !== name) {\n            await this.shutdownPlugin(dep);\n            this.removePluginFromGraph(dep);\n          }\n        }\n      } else if (options?.force) {\n        this.logger.warn(`Force removing ${name}, breaking: ${dependents.join(', ')}`);\n      } else {\n        throw new Error(`Cannot remove ${name}: required by ${dependents.join(', ')}`);\n      }\n    }\n\n    // Shutdown and remove\n    await this.shutdownPlugin(name);\n    this.removePluginFromGraph(name);\n\n    this.logger.info(`Plugin unregistered: ${name}`);\n  }\n\n  // =========================================================================\n  // Initialization\n  // =========================================================================\n\n  /**\n   * Initialize all registered plugins.\n   */\n  async initialize(): Promise<void> {","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts#L352-L388","documentation":"Thrown by EnhancedPluginRegistry.unregister(name) when other plugins depend on the target (dependencyGraph.getDependents(name) is non-empty) and neither options.cascade nor options.force is set. The registry refuses to remove a plugin that would leave dependents with a broken dependency; cascade instead shuts dependents down first (reverse removal order), and force removes while only logging which links are broken.","triggerScenarios":"unregister('core-utils') while 'my-plugin' declares a dependency on 'core-utils' via metadata.dependencies, without passing options; attempting teardown in the wrong order (shared plugin before its dependents); removing a base plugin during shutdown while feature plugins are still registered.","commonSituations":"Hot-swapping a shared library plugin that others import; partial unregisters during shutdown sequencing; test fixtures removing a common dependency while dependent plugins remain loaded.","solutions":["Unregister dependents first (leaf plugins), then the shared plugin","Pass { cascade: true } to remove the plugin together with its dependents in reverse order","Pass { force: true } only when you accept breaking dependents at runtime (registry logs a warning) - e.g. forced shutdown"],"exampleFix":"// before\nawait registry.unregister('core-utils'); // 'my-plugin' depends on it -> throws\n\n// after\nawait registry.unregister('core-utils', { cascade: true }); // removes dependents too\n// or remove leaves first:\nawait registry.unregister('my-plugin');\nawait registry.unregister('core-utils');","handlingStrategy":"fallback","validationCode":"const dependents = await getDependentsOf(registry, name); // mirror of dependencyGraph.getDependents\nif (dependents.length > 0 && !options?.cascade && !options?.force) {\n  // remove leaves first instead of removing a required base\n  for (const dep of dependents) {\n    await registry.unregister(dep);\n  }\n}\nawait registry.unregister(name);","typeGuard":null,"tryCatchPattern":"try {\n  await registry.unregister(name);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Cannot remove')) {\n    // rethrow as a typed signal so callers can choose cascade vs leaves-first\n    throw new PluginHasDependentsError(name, err.message);\n  }\n  throw err;\n}","preventionTips":["Model plugin teardown order explicitly: dependents before dependencies","Use { cascade: true } when removing a base plugin together with its dependents is intended","Reserve { force: true } for shutdown paths; it knowingly breaks dependent links"],"tags":["dependency-graph","plugin-registry","unregister","typescript"],"backgroundTag":"dependency-conflict","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"}