{"record":{"id":"368555cf85b1d745","repo":"ruvnet/ruflo","slug":"collection-collectionid-not-found","errorCode":null,"errorMessage":"Collection ${collectionId} not found","messagePattern":"Collection (.+?) not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugins/src/collections/collection-manager.ts","lineNumber":158,"sourceCode":"    this.resolvedEntries.set(collection.id, resolved);\n    this.enabledOverrides.set(collection.id, new Set());\n    this.disabledOverrides.set(collection.id, new Set());\n\n    // Register enabled plugins\n    for (const { entry, pluginName, plugin } of resolved) {\n      if (this.isPluginEnabledSync(collection.id, pluginName, entry.defaultEnabled)) {\n        await this.registerResolvedPlugin(plugin, entry);\n      }\n    }\n  }\n\n  /**\n   * Unload a plugin collection.\n   */\n  async unloadCollection(collectionId: string): Promise<void> {\n    const resolved = this.resolvedEntries.get(collectionId);\n    if (!resolved) {\n      throw new Error(`Collection ${collectionId} not found`);\n    }\n\n    // Unregister all plugins from this collection\n    for (const { pluginName } of resolved) {\n      if (this.registry.getPlugin(pluginName)) {\n        try {\n          await this.registry.unregister(pluginName);\n        } catch {\n          // Ignore errors during unload\n        }\n      }\n    }\n\n    this.collections.delete(collectionId);\n    this.resolvedEntries.delete(collectionId);\n    this.enabledOverrides.delete(collectionId);\n    this.disabledOverrides.delete(collectionId);\n  }","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugins/src/collections/collection-manager.ts#L140-L176","documentation":"unloadCollection looks the collection up in resolvedEntries; a miss means the manager never successfully loaded that collection (never attempted, failed during plugin resolution, or already unloaded) and the unload is rejected.","triggerScenarios":"Unloading a never-loaded collection ID; unloading the same collection twice; unloading after a loadCollection that threw during plugin resolution so resolvedEntries was never populated.","commonSituations":"Cleanup or teardown code that assumes collections exist; double teardown in test hooks; failed loads leaving no entry to unload.","solutions":["Only unload IDs that a prior successful loadCollection registered","Treat unload-of-unknown as a no-op in teardown paths (catch and continue)","After a failed load, skip the corresponding unload"],"exampleFix":"// before\nawait manager.unloadCollection('analytics'); // never loaded -> throws\n\n// after\nconst loaded = new Set<string>();\ntry {\n  await manager.loadCollection(col);\n  loaded.add('analytics');\n} catch { /* load failed: nothing to unload */ }\nif (loaded.has('analytics')) await manager.unloadCollection('analytics');","handlingStrategy":"validation","validationCode":"const loaded = new Set<string>();\ntry {\n  await manager.loadCollection(col);\n  loaded.add(col.id);\n} catch {\n  // load failed: there is nothing to unload\n}\n// teardown:\nfor (const id of loaded) await manager.unloadCollection(id);","typeGuard":null,"tryCatchPattern":"try {\n  await manager.unloadCollection(id);\n} catch (e) {\n  if (e instanceof Error && e.message.endsWith('not found')) return; // already clean\n  throw e;\n}","preventionTips":["Record IDs from successful loads and drive teardown from that record","Make teardown tolerant of already-unloaded state","Skip the unload when the matching load threw"],"tags":["plugins","collection-manager","entity-not-found","lifecycle"],"backgroundTag":"entity-not-found","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}