UAT: PluginManager.register_plugin() raises on duplicate but register_from_config() silently returns existing — inconsistent duplicate registration semantics #6034

Open
opened 2026-04-09 13:54:57 +00:00 by HAL9000 · 1 comment
Owner

Bug Report

Feature Area: Plugin Architecture — Plugin Registration
Severity: Medium
Milestone: v3.6.0 (plugin architecture scope)

What Was Tested

Code-level analysis of src/cleveragents/infrastructure/plugins/manager.py — comparing register_plugin() and register_from_config() duplicate handling.

Expected Behavior (from spec)

Both registration paths should have consistent semantics for duplicate plugin names. Either both should raise on duplicate, or both should silently skip/return the existing descriptor.

Actual Behavior (from code)

The two registration methods have different duplicate handling:

register_plugin() — raises on duplicate:

def register_plugin(self, descriptor: PluginDescriptor) -> None:
    with self._lock:
        if descriptor.name in self._plugins:
            msg = f"Plugin '{descriptor.name}' is already registered"
            raise PluginError(msg)  # ← RAISES

register_from_config() — silently returns existing:

def register_from_config(self, config: dict[str, Any]) -> PluginDescriptor | None:
    ...
    with self._lock:
        if name in self._plugins:
            self._logger.debug("plugin_manager.config_already_registered", name=name)
            return self._plugins[name]  # ← SILENTLY RETURNS EXISTING

This inconsistency means:

  • Code using register_plugin() must guard against PluginError for duplicates
  • Code using register_from_config() gets silent idempotent behavior
  • The discover() method (which calls load_from_entry_points()) also silently skips duplicates
  • There is no consistent contract for "what happens when I register the same plugin twice"

Code Location

src/cleveragents/infrastructure/plugins/manager.py:

  • register_plugin() — raises PluginError on duplicate
  • register_from_config() — returns existing descriptor silently
  • discover() — skips duplicates silently

Impact

  • Callers of register_plugin() must catch PluginError for idempotent registration patterns
  • The register_all_extension_points() scenario "Registering extension points twice does not duplicate" works for extension points (which use register_extension_point() which overwrites), but the plugin registration path is inconsistent
  • Integration code that mixes both registration paths will see different behavior for the same logical operation

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report **Feature Area**: Plugin Architecture — Plugin Registration **Severity**: Medium **Milestone**: v3.6.0 (plugin architecture scope) ## What Was Tested Code-level analysis of `src/cleveragents/infrastructure/plugins/manager.py` — comparing `register_plugin()` and `register_from_config()` duplicate handling. ## Expected Behavior (from spec) Both registration paths should have consistent semantics for duplicate plugin names. Either both should raise on duplicate, or both should silently skip/return the existing descriptor. ## Actual Behavior (from code) The two registration methods have **different duplicate handling**: ### `register_plugin()` — raises on duplicate: ```python def register_plugin(self, descriptor: PluginDescriptor) -> None: with self._lock: if descriptor.name in self._plugins: msg = f"Plugin '{descriptor.name}' is already registered" raise PluginError(msg) # ← RAISES ``` ### `register_from_config()` — silently returns existing: ```python def register_from_config(self, config: dict[str, Any]) -> PluginDescriptor | None: ... with self._lock: if name in self._plugins: self._logger.debug("plugin_manager.config_already_registered", name=name) return self._plugins[name] # ← SILENTLY RETURNS EXISTING ``` This inconsistency means: - Code using `register_plugin()` must guard against `PluginError` for duplicates - Code using `register_from_config()` gets silent idempotent behavior - The `discover()` method (which calls `load_from_entry_points()`) also silently skips duplicates - There is no consistent contract for "what happens when I register the same plugin twice" ## Code Location `src/cleveragents/infrastructure/plugins/manager.py`: - `register_plugin()` — raises `PluginError` on duplicate - `register_from_config()` — returns existing descriptor silently - `discover()` — skips duplicates silently ## Impact - Callers of `register_plugin()` must catch `PluginError` for idempotent registration patterns - The `register_all_extension_points()` scenario "Registering extension points twice does not duplicate" works for extension points (which use `register_extension_point()` which overwrites), but the plugin registration path is inconsistent - Integration code that mixes both registration paths will see different behavior for the same logical operation --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
HAL9000 added this to the v3.5.0 milestone 2026-04-09 14:36:08 +00:00
Author
Owner

Label compliance fix applied:

  • Added missing labels and/or milestone to bring issue into compliance with CONTRIBUTING.md

Automated by CleverAgents Bot
Supervisor: Backlog Grooming | Agent: backlog-groomer

Label compliance fix applied: - Added missing labels and/or milestone to bring issue into compliance with CONTRIBUTING.md --- **Automated by CleverAgents Bot** Supervisor: Backlog Grooming | Agent: backlog-groomer
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#6034
No description provided.