UAT: PluginManager.activate_plugin() allows re-activating a DEACTIVATED plugin — spec state machine requires DISCOVERED → ACTIVATED, not DEACTIVATED → ACTIVATED #6058

Open
opened 2026-04-09 14:15:21 +00:00 by HAL9000 · 0 comments
Owner

Bug Report

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

What Was Tested

Code-level analysis of src/cleveragents/infrastructure/plugins/manager.pyactivate_plugin() and deactivate_plugin() methods, and src/cleveragents/infrastructure/plugins/types.pyPluginState enum with state transition diagram.

Expected Behavior (from spec / state diagram)

The PluginState docstring defines the state machine:

DISCOVERED ──► ACTIVATED ──► EXECUTING ──► ACTIVATED
              │                              │
              └──► DEACTIVATED              └──► ERRORED
              │                                   │
              └──► ERRORED                        └──► DEACTIVATED

The valid transitions are:

  • DISCOVERED → ACTIVATED (via activate_plugin())
  • ACTIVATED → DEACTIVATED (via deactivate_plugin())
  • ACTIVATED → ERRORED (on activation failure)
  • ERRORED → DEACTIVATED (via deactivate_plugin())

There is no DEACTIVATED → ACTIVATED transition in the spec state machine.

Actual Behavior (from code)

activate_plugin() only guards against re-activation of ACTIVATED state:

def activate_plugin(self, name: str) -> None:
    with self._lock:
        descriptor = self.get_plugin(name)

        if descriptor.state == PluginState.ACTIVATED:
            msg = f"Plugin '{name}' is already activated"
            raise PluginError(msg)  # ← only guards ACTIVATED

        # DEACTIVATED state is NOT guarded — falls through to activation
        if not descriptor.module_path or not descriptor.class_name:
            ...
        try:
            cls = self._loader.load_class(...)
            instance = cls()
            ...
            descriptor.state = PluginState.ACTIVATED  # ← DEACTIVATED → ACTIVATED allowed!

A plugin that has been deactivated (state = DEACTIVATED) can be re-activated by calling activate_plugin() again. This is not in the spec state machine.

Code Location

src/cleveragents/infrastructure/plugins/manager.pyactivate_plugin() method.
src/cleveragents/infrastructure/plugins/types.pyPluginState state diagram.

Steps to Reproduce

  1. Register a plugin: manager.register_plugin(descriptor)
  2. Activate it: manager.activate_plugin(name) → state = ACTIVATED
  3. Deactivate it: manager.deactivate_plugin(name) → state = DEACTIVATED
  4. Re-activate it: manager.activate_plugin(name) → succeeds, state = ACTIVATED again
  5. This violates the spec state machine which has no DEACTIVATED → ACTIVATED edge

Impact

  • The plugin lifecycle state machine is not enforced correctly
  • A deactivated plugin can be re-activated without going through DISCOVERED state
  • This may cause unexpected behavior if deactivation is intended to be permanent (e.g., for security reasons)
  • The state diagram in PluginState docstring is misleading about what transitions are actually enforced

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

## Bug Report **Feature Area**: Plugin Architecture — Plugin Lifecycle State Machine **Severity**: Medium **Milestone**: v3.6.0 (plugin architecture scope) ## What Was Tested Code-level analysis of `src/cleveragents/infrastructure/plugins/manager.py` — `activate_plugin()` and `deactivate_plugin()` methods, and `src/cleveragents/infrastructure/plugins/types.py` — `PluginState` enum with state transition diagram. ## Expected Behavior (from spec / state diagram) The `PluginState` docstring defines the state machine: ``` DISCOVERED ──► ACTIVATED ──► EXECUTING ──► ACTIVATED │ │ └──► DEACTIVATED └──► ERRORED │ │ └──► ERRORED └──► DEACTIVATED ``` The valid transitions are: - `DISCOVERED → ACTIVATED` (via `activate_plugin()`) - `ACTIVATED → DEACTIVATED` (via `deactivate_plugin()`) - `ACTIVATED → ERRORED` (on activation failure) - `ERRORED → DEACTIVATED` (via `deactivate_plugin()`) There is **no** `DEACTIVATED → ACTIVATED` transition in the spec state machine. ## Actual Behavior (from code) `activate_plugin()` only guards against re-activation of `ACTIVATED` state: ```python def activate_plugin(self, name: str) -> None: with self._lock: descriptor = self.get_plugin(name) if descriptor.state == PluginState.ACTIVATED: msg = f"Plugin '{name}' is already activated" raise PluginError(msg) # ← only guards ACTIVATED # DEACTIVATED state is NOT guarded — falls through to activation if not descriptor.module_path or not descriptor.class_name: ... try: cls = self._loader.load_class(...) instance = cls() ... descriptor.state = PluginState.ACTIVATED # ← DEACTIVATED → ACTIVATED allowed! ``` A plugin that has been deactivated (state = `DEACTIVATED`) can be re-activated by calling `activate_plugin()` again. This is not in the spec state machine. ## Code Location `src/cleveragents/infrastructure/plugins/manager.py` — `activate_plugin()` method. `src/cleveragents/infrastructure/plugins/types.py` — `PluginState` state diagram. ## Steps to Reproduce 1. Register a plugin: `manager.register_plugin(descriptor)` 2. Activate it: `manager.activate_plugin(name)` → state = `ACTIVATED` 3. Deactivate it: `manager.deactivate_plugin(name)` → state = `DEACTIVATED` 4. Re-activate it: `manager.activate_plugin(name)` → succeeds, state = `ACTIVATED` again 5. This violates the spec state machine which has no `DEACTIVATED → ACTIVATED` edge ## Impact - The plugin lifecycle state machine is not enforced correctly - A deactivated plugin can be re-activated without going through `DISCOVERED` state - This may cause unexpected behavior if deactivation is intended to be permanent (e.g., for security reasons) - The state diagram in `PluginState` docstring is misleading about what transitions are actually enforced --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
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#6058
No description provided.