{"record":{"id":"8971308dd4fc5eec","repo":"ruvnet/ruflo","slug":"invalid-plugin-replacement","errorCode":null,"errorMessage":"Invalid plugin replacement","messagePattern":"Invalid plugin replacement","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts","lineNumber":555,"sourceCode":"    if (!entry) {\n      throw new Error(`Plugin ${name} not found`);\n    }\n\n    // Capture state if preserving\n    let state: unknown;\n    if (options?.preserveState && (entry.plugin as any).getState) {\n      state = await (entry.plugin as any).getState();\n    }\n\n    // Shutdown old plugin\n    if (entry.plugin.state === 'initialized') {\n      await entry.plugin.shutdown();\n    }\n\n    // Resolve and validate new plugin\n    const resolved = typeof newPlugin === 'function' ? await newPlugin() : newPlugin;\n    if (!validatePlugin(resolved)) {\n      throw new Error('Invalid plugin replacement');\n    }\n\n    // Verify same name\n    if (resolved.metadata.name !== name) {\n      throw new Error(`Plugin name mismatch: expected ${name}, got ${resolved.metadata.name}`);\n    }\n\n    // Update dependency graph\n    const dependencies = this.parseDependencies(resolved.metadata.dependencies);\n    this.dependencyGraph.removePlugin(name);\n    this.dependencyGraph.addPlugin(name, resolved.metadata.version, dependencies);\n\n    // Initialize new plugin\n    const context = this.createPluginContext(entry);\n    const timeout = options?.timeout ?? this.config.loadTimeout ?? 30000;\n\n    await Promise.race([\n      resolved.initialize(context),","sourceCodeStart":537,"sourceCodeEnd":573,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugins/src/registry/enhanced-plugin-registry.ts#L537-L573","documentation":"Thrown by EnhancedPluginRegistry.reload() after the old plugin has been shut down and the replacement resolved: validatePlugin(newPlugin) failed, so the swap is aborted because the replacement does not satisfy the IPlugin contract (metadata, lifecycle methods, state). Note ordering: the old plugin is already shut down when this throws, so the registry slot is left without a running plugin.","triggerScenarios":"Passing a replacement object or factory result that lacks required IPlugin members (metadata.name/version, init/shutdown); a factory that resolves to a partial or undefined value; swapping in a plugin built against an older SDK interface; module-namespace/default import confusion yielding a non-plugin object.","commonSituations":"Hot-reloading a freshly edited plugin whose export shape changed; the new plugin file having an export problem that still resolves to an object; SDK upgrade renaming lifecycle methods; dev workflow where a broken build is hot-swapped in.","solutions":["Run validatePlugin (or an equivalent shape check) on the replacement BEFORE calling reload, so the old plugin is not shut down for a doomed swap","Fix the replacement to implement the full IPlugin interface (metadata, init, shutdown, state)","If the factory can fail, make it throw rather than return a partial object, and validate in a try/catch before reload"],"exampleFix":"// before\nawait registry.reload('logger', async () => ({\n  metadata: { name: 'logger', version: '2.0.0' },\n})); // old plugin shut down, replacement lacks init/shutdown -> throws\n\n// after\nconst replacement = await buildLoggerPlugin(); // full IPlugin\nif (!isIPlugin(replacement)) {\n  throw new Error('replacement failed validation; keeping old plugin');\n}\nawait registry.reload('logger', replacement);","handlingStrategy":"type-guard","validationCode":"// Validate the replacement BEFORE reload so the old plugin is never shut down for a doomed swap\nconst resolved = typeof replacement === 'function' ? await replacement() : replacement;\nif (!isIPlugin(resolved)) {\n  throw new Error('replacement failed IPlugin validation; aborting hot-swap');\n}\nawait registry.reload(name, resolved);","typeGuard":"function isIPlugin(candidate: unknown): candidate is IPlugin {\n  if (typeof candidate !== 'object' || candidate === null) return false;\n  const p = candidate as Record<string, unknown>;\n  const meta = p.metadata as Record<string, unknown> | undefined;\n  return (\n    !!meta &&\n    typeof meta.name === 'string' &&\n    typeof meta.version === 'string' &&\n    typeof p.init === 'function' &&\n    typeof p.shutdown === 'function'\n  );\n}","tryCatchPattern":"try {\n  await registry.reload(name, newPlugin);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid plugin replacement') {\n    // old plugin is already shut down: re-register the previous build or a safe fallback\n    await registry.register(previousGoodPlugin);\n  }\n  throw err;\n}","preventionTips":["Run validatePlugin on the built artifact before initiating a hot-swap","Keep the last-known-good plugin available to restore when a swap fails","Type hot-swap payloads as IPlugin and compile-check them against the current SDK"],"tags":["plugin-validation","hot-reload","plugin-registry","typescript"],"backgroundTag":"plugin-validation-failed","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"}