BUG-HUNT: [error-handling] PluginError hierarchy does not inherit from CleverAgentsError — plugin failures bypass error classification, redaction, and produce raw Python tracebacks at the CLI #6641

Open
opened 2026-04-09 22:39:26 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: [error-handling] — PluginError not a CleverAgentsError subclass

Severity Assessment

  • Impact: Any PluginError, PluginLoadError, PluginNotFoundError, or ProtocolMismatchError raised during plugin operations propagates as a raw unhandled exception up to the CLI's top-level except Exception as e handler (or Typer's pretty_exceptions renderer), bypassing all error classification, HTTP-like error codes, and redaction logic. Users see a raw Python traceback or unformatted str(exc) instead of the structured error output mandated by ADR-005.
  • Likelihood: Triggers whenever a plugin fails to load, is not found, or has a protocol mismatch — normal user-facing scenarios.
  • Priority: High

Location

  • File: src/cleveragents/infrastructure/plugins/exceptions.py
  • Class: PluginError (base) and all subclasses
  • Lines: 12–34

Description

The entire plugin exception hierarchy is rooted at a plain Exception subclass that does not inherit from CleverAgentsError:

# src/cleveragents/infrastructure/plugins/exceptions.py
class PluginError(Exception):          # ← plain Exception, NOT CleverAgentsError
    """Base exception for all plugin-related errors."""

class PluginLoadError(PluginError):    # inherits plain Exception
    ...

class PluginNotFoundError(PluginError):  # inherits plain Exception
    ...

class ProtocolMismatchError(PluginError):  # inherits plain Exception
    ...

The project's error classification system in core/error_handling.py only knows how to classify CleverAgentsError subclasses:

# core/error_handling.py
_EXCEPTION_CODE_MAP: dict[type[Exception], ErrorCode] = {
    ValidationError: ErrorCode.VALIDATION_FAILED,
    ResourceNotFoundError: ErrorCode.NOT_FOUND,
    ...
    CleverAgentsError: ErrorCode.INTERNAL,  # catch-all, only for CleverAgentsError
}

Since PluginError is not in _EXCEPTION_CODE_MAP and doesn't inherit from CleverAgentsError, classify_error() will assign ErrorCode.INTERNAL at best only if wrapped — but because the CLI command handlers catch CleverAgentsError before Exception, any uncaught PluginError falls through to the bare except Exception as e blocks which print raw str(e) without going through wrap_unexpected() or redact_value().

The A2A map_domain_error() function in a2a/errors.py likewise has no mapping for PluginError:

# a2a/errors.py — map_domain_error()
if isinstance(exc, CleverAgentsError):
    return INTERNAL_ERROR, str(exc)
return INTERNAL_ERROR, str(exc)  # ← plugin errors match here but str(exc) is unredacted

While A2A returns INTERNAL_ERROR for plugin errors (acceptable), the str(exc) can contain unredacted internal details.

Evidence

# src/cleveragents/infrastructure/plugins/exceptions.py (lines 12-33)
class PluginError(Exception):          # ← NOT CleverAgentsError
    """Base exception for all plugin-related errors."""

class PluginLoadError(PluginError):
    """Raised when a plugin module cannot be found or imported."""

class PluginNotFoundError(PluginError):
    """Raised when a requested plugin is not in the registry."""

class ProtocolMismatchError(PluginError):
    """Raised when a loaded class does not satisfy the expected Protocol."""

In the plugin manager (infrastructure/plugins/manager.py), the errors are raised without any wrapping:

raise PluginError(msg)      # → not CleverAgentsError
raise PluginLoadError(msg)  # → not CleverAgentsError

Compare with the LSP subsystem which correctly inherits:

# src/cleveragents/lsp/errors.py
class LspError(CleverAgentsError):  # ← correctly inherits
    ...

Expected Behavior

PluginError and all its subclasses should inherit from CleverAgentsError (matching the pattern established by LspError, A2aError, etc.), so that they:

  1. Are captured by except CleverAgentsError in CLI command handlers
  2. Are classified and formatted by classify_error()format_error_for_cli()
  3. Have their details redacted before display

Actual Behavior

Plugin errors bypass all structured error handling and produce either:

  • A raw Python traceback (if Typer's pretty_exceptions_enable=True fires)
  • An unredacted str(exc) string via bare except Exception as e handlers in CLI commands

Suggested Fix

Change PluginError to inherit from CleverAgentsError and add it to _EXCEPTION_CODE_MAP:

# src/cleveragents/infrastructure/plugins/exceptions.py
from cleveragents.core.exceptions import CleverAgentsError

class PluginError(CleverAgentsError):
    """Base exception for all plugin-related errors."""

class PluginLoadError(PluginError):
    ...

And in core/error_handling.py:

from cleveragents.infrastructure.plugins.exceptions import PluginError, PluginNotFoundError

_EXCEPTION_CODE_MAP[PluginNotFoundError] = ErrorCode.NOT_FOUND
_EXCEPTION_CODE_MAP[PluginLoadError] = ErrorCode.INTERNAL
_EXCEPTION_CODE_MAP[PluginError] = ErrorCode.INTERNAL

(Be aware: this may introduce a circular import — consider moving PluginError to core/exceptions.py or using lazy imports.)

Category

error-handling / consistency

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [error-handling] — `PluginError` not a `CleverAgentsError` subclass ### Severity Assessment - **Impact**: Any `PluginError`, `PluginLoadError`, `PluginNotFoundError`, or `ProtocolMismatchError` raised during plugin operations propagates as a raw unhandled exception up to the CLI's top-level `except Exception as e` handler (or Typer's `pretty_exceptions` renderer), bypassing all error classification, HTTP-like error codes, and redaction logic. Users see a raw Python traceback or unformatted `str(exc)` instead of the structured error output mandated by ADR-005. - **Likelihood**: Triggers whenever a plugin fails to load, is not found, or has a protocol mismatch — normal user-facing scenarios. - **Priority**: High ### Location - **File**: `src/cleveragents/infrastructure/plugins/exceptions.py` - **Class**: `PluginError` (base) and all subclasses - **Lines**: 12–34 ### Description The entire plugin exception hierarchy is rooted at a plain `Exception` subclass that **does not inherit from `CleverAgentsError`**: ```python # src/cleveragents/infrastructure/plugins/exceptions.py class PluginError(Exception): # ← plain Exception, NOT CleverAgentsError """Base exception for all plugin-related errors.""" class PluginLoadError(PluginError): # inherits plain Exception ... class PluginNotFoundError(PluginError): # inherits plain Exception ... class ProtocolMismatchError(PluginError): # inherits plain Exception ... ``` The project's error classification system in `core/error_handling.py` only knows how to classify `CleverAgentsError` subclasses: ```python # core/error_handling.py _EXCEPTION_CODE_MAP: dict[type[Exception], ErrorCode] = { ValidationError: ErrorCode.VALIDATION_FAILED, ResourceNotFoundError: ErrorCode.NOT_FOUND, ... CleverAgentsError: ErrorCode.INTERNAL, # catch-all, only for CleverAgentsError } ``` Since `PluginError` is not in `_EXCEPTION_CODE_MAP` and doesn't inherit from `CleverAgentsError`, `classify_error()` will assign `ErrorCode.INTERNAL` at best only if wrapped — but because the CLI command handlers catch `CleverAgentsError` before `Exception`, any uncaught `PluginError` falls through to the bare `except Exception as e` blocks which print raw `str(e)` without going through `wrap_unexpected()` or `redact_value()`. The A2A `map_domain_error()` function in `a2a/errors.py` likewise has no mapping for `PluginError`: ```python # a2a/errors.py — map_domain_error() if isinstance(exc, CleverAgentsError): return INTERNAL_ERROR, str(exc) return INTERNAL_ERROR, str(exc) # ← plugin errors match here but str(exc) is unredacted ``` While A2A returns INTERNAL_ERROR for plugin errors (acceptable), the `str(exc)` can contain unredacted internal details. ### Evidence ```python # src/cleveragents/infrastructure/plugins/exceptions.py (lines 12-33) class PluginError(Exception): # ← NOT CleverAgentsError """Base exception for all plugin-related errors.""" class PluginLoadError(PluginError): """Raised when a plugin module cannot be found or imported.""" class PluginNotFoundError(PluginError): """Raised when a requested plugin is not in the registry.""" class ProtocolMismatchError(PluginError): """Raised when a loaded class does not satisfy the expected Protocol.""" ``` In the plugin manager (`infrastructure/plugins/manager.py`), the errors are raised without any wrapping: ```python raise PluginError(msg) # → not CleverAgentsError raise PluginLoadError(msg) # → not CleverAgentsError ``` Compare with the LSP subsystem which correctly inherits: ```python # src/cleveragents/lsp/errors.py class LspError(CleverAgentsError): # ← correctly inherits ... ``` ### Expected Behavior `PluginError` and all its subclasses should inherit from `CleverAgentsError` (matching the pattern established by `LspError`, `A2aError`, etc.), so that they: 1. Are captured by `except CleverAgentsError` in CLI command handlers 2. Are classified and formatted by `classify_error()` → `format_error_for_cli()` 3. Have their details redacted before display ### Actual Behavior Plugin errors bypass all structured error handling and produce either: - A raw Python traceback (if Typer's `pretty_exceptions_enable=True` fires) - An unredacted `str(exc)` string via bare `except Exception as e` handlers in CLI commands ### Suggested Fix Change `PluginError` to inherit from `CleverAgentsError` and add it to `_EXCEPTION_CODE_MAP`: ```python # src/cleveragents/infrastructure/plugins/exceptions.py from cleveragents.core.exceptions import CleverAgentsError class PluginError(CleverAgentsError): """Base exception for all plugin-related errors.""" class PluginLoadError(PluginError): ... ``` And in `core/error_handling.py`: ```python from cleveragents.infrastructure.plugins.exceptions import PluginError, PluginNotFoundError _EXCEPTION_CODE_MAP[PluginNotFoundError] = ErrorCode.NOT_FOUND _EXCEPTION_CODE_MAP[PluginLoadError] = ErrorCode.INTERNAL _EXCEPTION_CODE_MAP[PluginError] = ErrorCode.INTERNAL ``` (Be aware: this may introduce a circular import — consider moving `PluginError` to `core/exceptions.py` or using lazy imports.) ### Category error-handling / consistency ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:47:14 +00:00
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.

Dependencies

No dependencies set.

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