Skip to content

Conversation

@Sainava
Copy link

@Sainava Sainava commented Jan 4, 2026

Closes #213

📝 Description

This PR completes 7 incomplete implementations across the codebase by replacing empty pass statements with production-ready code including proper error handling, logging, and retry mechanisms.

🔧 Changes Made

  • Event Bus (event_bus.py): Added duplicate handler prevention and comprehensive logging for event registration
  • Discord Views (views.py): Replaced generic exception handling with specific Discord exceptions (Forbidden, NotFound, HTTPException) and detailed error logging
  • Search Tool (ddg.py): Implemented configurable timeout, retry logic with exponential backoff, and optional result caching

All changes are backward compatible with no breaking changes to existing APIs.

📷 Screenshots or Visual Changes (if applicable)

N/A - Backend changes only, no visual changes.

🤝 Collaboration

N/A

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • Search supports optional caching, configurable timeouts/retries, and exponential backoff with jitter.
  • Improvements

    • Search returns standardized result items, provides per-attempt logging, timeout handling, and resilient retry behavior.
    • Event system adds registration/dispatch logging and prevents duplicate handler registrations.
    • Integrations improve logging and handle specific error cases for messaging and onboarding instead of failing silently.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Added robust logging and duplicate-registration checks to the event bus; hardened Discord view handlers with explicit logging for exceptions; and enhanced the DuckDuckGo search tool with configurable timeout/retries, optional caching, asyncio timeouts, exponential backoff with jitter, and detailed per-attempt logging and caching.

Changes

Cohort / File(s) Change summary
Search tool
backend/app/agents/devrel/tools/search_tool/ddg.py
Replaced no-arg constructor with __init__(timeout: int = 10, max_retries: int = 2, cache_enabled: bool = False, base_delay: float = 1.0, max_delay: float = 10.0); added optional in-memory cache, detailed search() with retries, asyncio timeout, exponential backoff + jitter, per-attempt logging, error-specific handling, standardized result shaping, and cache reads/writes. Public search(query, max_results=5) interface preserved.
Event bus
backend/app/core/events/event_bus.py
Added logger, registration logging, duplicate-handler prevention for event-specific and global handlers (warnings on duplicates), debug/info logs when creating handler lists and adding handlers, optional platform-filter logging, and more-verbose dispatch logs when invoking global and event-type handlers or when none exist.
Discord integration (views)
backend/integrations/discord/views.py
Added module logger; replaced silent except/pass blocks with explicit handling and logging for discord.Forbidden, discord.NotFound, and generic exceptions on DM sends and onboarding view edits; logs successes and failures with appropriate levels.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DuckDuckGoSearchTool as Tool
  participant ExternalDDG as DDG_API
  participant Cache
  Note over Tool: Configured (timeout, retries, caching, backoff)

  Client->>Tool: search(query)
  alt Cache enabled
    Tool->>Cache: compute & lookup key
    Cache-->>Tool: cached results (hit) or miss
    alt hit
      Tool-->>Client: return cached results
    else miss
      Tool->>DDG_API: request (with timeout)
      alt success
        DDG_API-->>Tool: results
        Tool->>Cache: store results
        Tool-->>Client: return results
      else timeout/conn error
        Tool->>Tool: backoff + retry (up to max_retries)
        Tool->>DDG_API: retry...
        Tool-->>Client: [] after retries exhausted
      end
    end
  else Cache disabled
    Tool->>DDG_API: request (with timeout)
    alt success
      DDG_API-->>Tool: results
      Tool-->>Client: return results
    else retries/backoff similar to above
      Tool-->>Client: [] after retries exhausted
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • smokeyScraper

Poem

"A rabbit hopped and tuned the search,
I jittered back when nets would lurch.
Retries and caches snug and bright,
Logs to guide the midnight flight.
Hooray — my hops found answers right! 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: replace empty pass statements with error handling and logging' accurately summarizes the main objective of the pull request.
Linked Issues check ✅ Passed All code changes align with objectives from issue #213: event bus duplicate handler prevention and logging, Discord views specific exception handling with logging, and search tool timeout/retry logic with caching.
Out of Scope Changes check ✅ Passed All changes are directly in scope: the three target files (event_bus.py, discord/views.py, ddg.py) are being enhanced as specified in issue #213 with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b204229 and 4d99091.

📒 Files selected for processing (1)
  • backend/integrations/discord/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/integrations/discord/views.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/core/events/event_bus.py (1)

18-29: Platform filter parameter is accepted but never enforced during dispatch.

The platform parameter in register_handler() (line 18) is only logged at line 29 and never stored. The handler is added to the registry via _add_handler() without the platform constraint, and during dispatch (lines 54-68), handlers are retrieved and invoked without any filtering by platform. If platform filtering is intended, handlers must be stored with their platform constraint and filtered during dispatch.

🧹 Nitpick comments (4)
backend/integrations/discord/views.py (1)

36-41: Use logger.exception to preserve stack traces.

For the generic Exception handler, logger.exception automatically includes the traceback, improving debuggability. The same applies to lines 111-114 and 131-134.

Proposed fix
     except discord.HTTPException as e:
         logger.error(f"Discord API error sending DM to user {user.id}: {e.status} - {e.text}")
     except Exception as e:
-        logger.error(f"Unexpected error sending DM to user {user.id}: {type(e).__name__} - {str(e)}")
+        logger.exception(f"Unexpected error sending DM to user {user.id}: {type(e).__name__}")

Apply the same pattern to the other exception handlers at lines 113-114 and 133-134.

backend/app/core/events/event_bus.py (1)

54-68: Consider log level and error handling for fire-and-forget tasks.

Two observations:

  1. Log level: INFO-level logging on every handler invocation (lines 59, 65) could be noisy under high event volume. Consider DEBUG level.

  2. Unhandled exceptions: asyncio.create_task without exception handling means handler errors are silently lost (only logged to asyncio's default handler). Consider wrapping handlers or adding task.add_done_callback() for visibility.

Optional: Add exception callback for task monitoring
async def dispatch(self, event: BaseEvent):
    """Dispatch an event to all registered handlers"""
    
    def _log_exception(task):
        if task.exception():
            logger.error(f"Handler failed: {task.exception()}")
    
    for handler in self.global_handlers:
        logger.debug(f"Calling global handler: {handler.__name__}")
        task = asyncio.create_task(handler(event))
        task.add_done_callback(_log_exception)
    # ... same for event-specific handlers
backend/app/agents/devrel/tools/search_tool/ddg.py (2)

18-23: Unbounded cache can cause memory growth.

The _cache dict grows indefinitely without eviction. For long-running services, this could lead to memory issues.

Consider adding a max size or TTL, or use functools.lru_cache / cachetools.TTLCache:

Proposed fix using bounded cache
+from cachetools import TTLCache
+
 class DuckDuckGoSearchTool:
-    def __init__(self, timeout: int = 10, max_retries: int = 2, cache_enabled: bool = False):
+    def __init__(self, timeout: int = 10, max_retries: int = 2, cache_enabled: bool = False, cache_maxsize: int = 100, cache_ttl: int = 300):
         self.timeout = timeout
         self.max_retries = max_retries
         self.cache_enabled = cache_enabled
-        self._cache: dict = {} if cache_enabled else None
+        self._cache = TTLCache(maxsize=cache_maxsize, ttl=cache_ttl) if cache_enabled else None

91-96: Use logger.exception to preserve stack traces.

For generic exception handlers, logger.exception automatically includes the traceback for better debuggability.

Proposed fix
             except Exception as e:
                 last_exception = e
-                logger.error(f"Search error (attempt {attempt + 1}/{self.max_retries + 1}): {str(e)}")
+                logger.exception(f"Search error (attempt {attempt + 1}/{self.max_retries + 1})")
                 if attempt < self.max_retries:
                     await asyncio.sleep(1)
                 continue
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa66519 and 332fd0e.

📒 Files selected for processing (3)
  • backend/app/agents/devrel/tools/search_tool/ddg.py
  • backend/app/core/events/event_bus.py
  • backend/integrations/discord/views.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-08T13:31:11.572Z
Learnt from: smokeyScraper
Repo: AOSSIE-Org/Devr.AI PR: 72
File: backend/app/agents/devrel/nodes/handle_web_search_node.py:31-42
Timestamp: 2025-06-08T13:31:11.572Z
Learning: In backend/app/agents/devrel/tools/search_tool.py, the TavilySearchTool.search() method has partial error handling for missing API key, AttributeError, ConnectionError, and TimeoutError, but lacks a comprehensive Exception catch-all block, so calling functions may still need additional error handling for other potential exceptions.

Applied to files:

  • backend/app/agents/devrel/tools/search_tool/ddg.py
🧬 Code graph analysis (1)
backend/app/core/events/event_bus.py (1)
backend/app/core/events/enums.py (1)
  • EventType (10-32)
🪛 Ruff (0.14.10)
backend/app/agents/devrel/tools/search_tool/ddg.py

75-75: Consider moving this statement to an else block

(TRY300)


91-91: Do not catch blind exception: Exception

(BLE001)


93-93: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


93-93: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backend/integrations/discord/views.py

39-39: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


40-40: Do not catch blind exception: Exception

(BLE001)


41-41: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


41-41: Use explicit conversion flag

Replace with conversion flag

(RUF010)


112-112: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


113-113: Do not catch blind exception: Exception

(BLE001)


114-114: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


114-114: Use explicit conversion flag

Replace with conversion flag

(RUF010)


132-132: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


133-133: Do not catch blind exception: Exception

(BLE001)


134-134: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


134-134: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (7)
backend/integrations/discord/views.py (3)

1-12: LGTM!

Logger initialization follows Python conventions with __name__ for proper module-level namespacing.


109-114: Good implementation of specific Discord exception handling.

The cascading exception hierarchy (NotFound → HTTPException → Exception) correctly handles the Discord API errors as required by the issue objectives.


129-134: Consistent error handling pattern.

Same recommendation applies here regarding logger.exception for the generic Exception handler (already noted above).

backend/app/core/events/event_bus.py (2)

31-42: LGTM!

Duplicate handler prevention is correctly implemented using identity check. The logging provides good visibility into the registration process.


44-52: LGTM!

Consistent duplicate prevention for global handlers with appropriate logging.

backend/app/agents/devrel/tools/search_tool/ddg.py (2)

29-44: LGTM!

Cache key construction and lookup logic is correct. Early return on cache hit improves performance.


98-100: LGTM!

Graceful degradation by returning an empty list after exhausting retries is appropriate. The final error log captures sufficient context.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
backend/app/agents/devrel/tools/search_tool/ddg.py (2)

21-29: Consider cache size limits and thread safety.

The cache implementation uses an unbounded dictionary without TTL or size limits, which could lead to memory growth in production. Additionally, the dictionary is not thread-safe—concurrent searches could cause race conditions during cache reads/writes.

Consider:

  • Adding a maximum cache size with LRU eviction (e.g., using functools.lru_cache or a bounded dict)
  • Implementing cache entry TTL to prevent stale results
  • Using asyncio.Lock to protect cache operations in concurrent scenarios

83-105: Exponential backoff with jitter correctly implemented!

The retry logic now properly implements exponential backoff with jitter across all exception handlers (lines 87, 95, 103), correctly addressing the previous review feedback. The formula delay = min(self.base_delay * (2 ** attempt) + random.uniform(-0.1, 0.1), self.max_delay) provides resilient retry behavior with bounded delays.

Minor improvement: Line 101 uses logging.error—consider using logging.exception instead to automatically capture the stack trace for better debugging:

-                logger.error(f"Search error (attempt {attempt + 1}/{self.max_retries + 1}): {str(e)}")
+                logger.exception(f"Search error (attempt {attempt + 1}/{self.max_retries + 1}): {e}")

Note: Catching both asyncio.TimeoutError and TimeoutError together (line 83) is appropriate for Python version compatibility.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 332fd0e and 3b64e16.

📒 Files selected for processing (1)
  • backend/app/agents/devrel/tools/search_tool/ddg.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-08T13:31:11.572Z
Learnt from: smokeyScraper
Repo: AOSSIE-Org/Devr.AI PR: 72
File: backend/app/agents/devrel/nodes/handle_web_search_node.py:31-42
Timestamp: 2025-06-08T13:31:11.572Z
Learning: In backend/app/agents/devrel/tools/search_tool.py, the TavilySearchTool.search() method has partial error handling for missing API key, AttributeError, ConnectionError, and TimeoutError, but lacks a comprehensive Exception catch-all block, so calling functions may still need additional error handling for other potential exceptions.

Applied to files:

  • backend/app/agents/devrel/tools/search_tool/ddg.py
🧬 Code graph analysis (1)
backend/app/agents/devrel/tools/search_tool/ddg.py (1)
backend/app/agents/devrel/tools/search_tool/tavilly.py (1)
  • search (17-52)
🪛 Ruff (0.14.10)
backend/app/agents/devrel/tools/search_tool/ddg.py

81-81: Consider moving this statement to an else block

(TRY300)


87-87: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


95-95: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


99-99: Do not catch blind exception: Exception

(BLE001)


101-101: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


101-101: Use explicit conversion flag

Replace with conversion flag

(RUF010)


103-103: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)

Copy link
Contributor

@Aditya30ag Aditya30ag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d suggest a simpler pattern (e.g., NotFound - debug, plus a single logger.exception for unexpected errors) to keep this readable and maintainable.

Comment on lines 36 to 41
except discord.Forbidden:
logger.warning(f"Cannot send DM to user {user.id} - DMs are disabled or bot is blocked")
except discord.HTTPException as e:
logger.error(f"Discord API error sending DM to user {user.id}: {e.status} - {e.text}")
except Exception as e:
logger.error(f"Unexpected error sending DM to user {user.id}: {type(e).__name__} - {str(e)}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except discord.Forbidden:
    logger.debug(f"Cannot send DM to user {user.id}")
except Exception:
    logger.exception("Failed to send handoff DM")

Comment on lines 109 to 114
except discord.NotFound:
logger.warning(f"Message not found when editing onboarding view for user {interaction.user.id}")
except discord.HTTPException as e:
logger.error(f"Failed to edit onboarding view: {e.status} - {e.text}")
except Exception as e:
logger.error(f"Unexpected error editing onboarding view: {type(e).__name__} - {str(e)}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except discord.NotFound:
    logger.debug("Interaction message no longer exists")
except Exception:
    logger.exception("Failed to update interaction view")

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @backend/integrations/discord/views.py:
- Around line 107-110: The exception log uses an f-string prefix without
placeholders and lacks context; replace the line logger.exception(f"Unexpected
error editing onboarding view") with a contextual log that includes the user and
interaction identifiers (e.g., interaction.user.id and interaction.id or
channel_id) and keep logger.exception to capture the traceback—for example, use
logger.exception("Unexpected error editing onboarding view for user=%s
interaction=%s", interaction.user.id, getattr(interaction, "id", None)) or an
equivalent f-string that actually interpolates interaction.user.id and
interaction.id.
- Around line 125-128: In the generic except block in the skip-view edit
handler, capture the exception (change "except Exception:" to "except Exception
as e:"), remove the unnecessary f-string prefix or include placeholders, and log
useful context; e.g. call logger.exception with a message that includes
interaction.user.id and the exception (or use exc_info=True) so the log becomes
something like "Unexpected error editing skip view for user
{interaction.user.id}: {e}" ensuring the user id and error details are present.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b64e16 and b204229.

📒 Files selected for processing (1)
  • backend/integrations/discord/views.py
🧰 Additional context used
🪛 Ruff (0.14.10)
backend/integrations/discord/views.py

110-110: f-string without any placeholders

Remove extraneous f prefix

(F541)


128-128: f-string without any placeholders

Remove extraneous f prefix

(F541)

🔇 Additional comments (2)
backend/integrations/discord/views.py (2)

1-1: LGTM! Standard logging setup.

The module-level logger follows Python best practices and enables structured logging throughout the module.

Also applies to: 11-12


30-40: The code follows the established exception handling pattern used consistently throughout the Discord integration module. While the PR objectives mention discord.HTTPException, the codebase does not implement explicit HTTPException handling anywhere. Instead, the pattern across cogs.py and views.py is to catch specific Discord exceptions (like discord.Forbidden and discord.NotFound) followed by a generic Exception handler, which implicitly captures all other exceptions including HTTPException. The current implementation in send_final_handoff_dm is correct and consistent with this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEATURE REQUEST:Complete incomplete implementations with proper error handling and logging

2 participants