BUG-HUNT: [error-handling] retry_auto_debug sync-callable check deferred to call-time instead of decoration-time #7751

Open
opened 2026-04-12 03:24:35 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: Error Handling — retry_auto_debug Sync Check at Wrong Time

Severity Assessment

  • Impact: Applying @retry_auto_debug() to a synchronous function raises no error at decoration time. The TypeError is only raised when the decorated function is actually called. This means misuse goes undetected during import, class definition, or decoration, and only manifests at runtime when the code path is exercised.
  • Likelihood: Medium — any developer applying this async-only decorator to a sync function will be surprised by a deferred error.
  • Priority: Medium

Location

  • File: src/cleveragents/core/retry_service_patterns.py
  • Function/Class: retry_auto_debug (inner decoratorwrapper)
  • Lines: 570–578

Description

The retry_auto_debug decorator is designed exclusively for async callables. The guard if not _is_async_callable(func): raise TypeError(...) is placed inside the async def wrapper function — which means it runs only when the wrapped function is called, not when the decorator is applied.

This violates the principle of fail-fast: errors about API misuse should be surfaced at decoration time (during import or class creation), not at call time (potentially in production execution).

Evidence

# src/cleveragents/core/retry_service_patterns.py lines 570-578
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        # S11: retry_auto_debug only supports async callables.
        if not _is_async_callable(func):          # <-- BUG: checked at call-time
            raise TypeError(
                "retry_auto_debug only supports async callables; "
                f"got sync function {func!r}"
            )
        ...
    return wrapper

The check if not _is_async_callable(func) should be in decorator(func), not inside wrapper:

# Current (wrong): TypeError raised only when wrapper() is called
def decorator(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        if not _is_async_callable(func):  # too late
            raise TypeError(...)

# Correct: TypeError raised when @retry_auto_debug() is applied
def decorator(func):
    if not _is_async_callable(func):      # fail fast
        raise TypeError(...)
    @wraps(func)
    async def wrapper(*args, **kwargs):
        ...
    return wrapper

Expected Behavior

Applying @retry_auto_debug() to a synchronous function should immediately raise TypeError at decoration time (e.g., at module import or class body execution).

Actual Behavior

@retry_auto_debug() applied to a sync function succeeds silently. The TypeError is only raised when the wrapped function is called, which may be much later in execution — potentially only in a specific code path that is rarely exercised.

Suggested Fix

Move the _is_async_callable check to the decorator function body, before wrapper is defined:

def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
    # S11: retry_auto_debug only supports async callables.
    if not _is_async_callable(func):
        raise TypeError(
            "retry_auto_debug only supports async callables; "
            f"got sync function {func!r}"
        )

    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> Any:
        last_error: Exception | str | None = None
        ...
    return wrapper

Category

error-handling

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


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

## Bug Report: Error Handling — retry_auto_debug Sync Check at Wrong Time ### Severity Assessment - **Impact**: Applying `@retry_auto_debug()` to a synchronous function raises no error at decoration time. The `TypeError` is only raised when the decorated function is actually called. This means misuse goes undetected during import, class definition, or decoration, and only manifests at runtime when the code path is exercised. - **Likelihood**: Medium — any developer applying this async-only decorator to a sync function will be surprised by a deferred error. - **Priority**: Medium ### Location - **File**: `src/cleveragents/core/retry_service_patterns.py` - **Function/Class**: `retry_auto_debug` (inner `decorator` → `wrapper`) - **Lines**: 570–578 ### Description The `retry_auto_debug` decorator is designed exclusively for async callables. The guard `if not _is_async_callable(func): raise TypeError(...)` is placed **inside the `async def wrapper`** function — which means it runs only when the wrapped function is **called**, not when the decorator is **applied**. This violates the principle of fail-fast: errors about API misuse should be surfaced at decoration time (during import or class creation), not at call time (potentially in production execution). ### Evidence ```python # src/cleveragents/core/retry_service_patterns.py lines 570-578 def decorator(func: Callable[..., Any]) -> Callable[..., Any]: @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: # S11: retry_auto_debug only supports async callables. if not _is_async_callable(func): # <-- BUG: checked at call-time raise TypeError( "retry_auto_debug only supports async callables; " f"got sync function {func!r}" ) ... return wrapper ``` The check `if not _is_async_callable(func)` should be in `decorator(func)`, not inside `wrapper`: ```python # Current (wrong): TypeError raised only when wrapper() is called def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): if not _is_async_callable(func): # too late raise TypeError(...) # Correct: TypeError raised when @retry_auto_debug() is applied def decorator(func): if not _is_async_callable(func): # fail fast raise TypeError(...) @wraps(func) async def wrapper(*args, **kwargs): ... return wrapper ``` ### Expected Behavior Applying `@retry_auto_debug()` to a synchronous function should immediately raise `TypeError` at decoration time (e.g., at module import or class body execution). ### Actual Behavior `@retry_auto_debug()` applied to a sync function succeeds silently. The `TypeError` is only raised when the wrapped function is called, which may be much later in execution — potentially only in a specific code path that is rarely exercised. ### Suggested Fix Move the `_is_async_callable` check to the `decorator` function body, before `wrapper` is defined: ```python def decorator(func: Callable[..., Any]) -> Callable[..., Any]: # S11: retry_auto_debug only supports async callables. if not _is_async_callable(func): raise TypeError( "retry_auto_debug only supports async callables; " f"got sync function {func!r}" ) @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: last_error: Exception | str | None = None ... return wrapper ``` ### Category error-handling ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:44:28 +00:00
Author
Owner

Verified — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: retry_auto_debug sync-callable check deferred to call-time instead of decoration-time. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
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#7751
No description provided.