Skip to content

Comments

Feature/add admin commands#277

Open
SiddhantGahankari wants to merge 18 commits intoAOSSIE-Org:mainfrom
SiddhantGahankari:feature/add-admin-commands
Open

Feature/add admin commands#277
SiddhantGahankari wants to merge 18 commits intoAOSSIE-Org:mainfrom
SiddhantGahankari:feature/add-admin-commands

Conversation

@SiddhantGahankari
Copy link

@SiddhantGahankari SiddhantGahankari commented Feb 16, 2026

Closes #202

📝 Description

This PR completes Discord admin command implementation by wiring commands to the admin service layer, replacing placeholder behavior, and improving operational safety for admin actions.

It also improves reliability by extending health coverage and validating admin logging readiness at startup.

🔧 Changes Made

  • Wired /admin commands to real admin service functions
  • Replaced placeholder command responses with actual logic
  • Added confirmation flow for destructive actions:
    • user_reset
    • queue_clear
    • cache_clear
  • Included FalkorDB in aggregate health checks
  • Added startup validation for admin_logs table and safe logging fallback
  • Updated admin-related tests and import/path handling for script-based execution
  • Replaced extra debug prints with structured logging.

✅ 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

    • /admin command group: stats, health, user_info, user_reset (with confirmations), queue_status, queue_clear, cache_clear.
    • Queue, cache, and user-management controls exposed to admins.
  • Monitoring

    • Built-in health checks and bot statistics reporting.
  • Audit / Database

    • Admin action auditing persisted to a new admin logs table (migration included).
  • Permissions

    • Admin & bot-owner permission enforcement with user-facing denial messages.
  • Documentation

    • New admin commands guide with setup and troubleshooting.
  • Env

    • New BOT_OWNER_ID environment variable.
  • Tests

    • Added integration and unit tests covering admin features, permissions, and monitoring.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Adds a permissioned admin command system and supporting services: telemetry, health checks, queue and cache management, user administration, admin action logging with DB migration, Discord integration (cog, permissions, views), startup wiring, documentation, and extensive tests.

Changes

Cohort / File(s) Summary
Project Configuration
\.gitignore, env.example, backend/app/core/config/settings.py
Uncommented .python-version in .gitignore and removed poetry.lock from ignore (so poetry.lock will be tracked); added BOT_OWNER_ID to env.example and bot_owner_id setting.
Admin Services Core
backend/app/services/admin/__init__.py, backend/app/services/admin/bot_stats_service.py, backend/app/services/admin/health_check_service.py, backend/app/services/admin/cache_service.py
New admin service modules and exports: BotStatsService (aggregates message/queue/system stats), HealthCheckService (checks Supabase, RabbitMQ, Weaviate, FalkorDB, Gemini), CacheService (inspect/clear caches) and package re-exports.
Admin Services Data/Management
backend/app/services/admin/queue_service.py, backend/app/services/admin/user_info_service.py, backend/app/services/admin/user_management_service.py
New QueueService (stats & purge by priority), UserInfoService (Supabase-backed user profile/metrics), UserManagementService (reset memory, close threads, reset verification) with result aggregation.
Admin Logging and Database
backend/app/utils/admin_logger.py, backend/database/02_create_admin_logs_table.sql
Added admin_logs migration (indexes, RLS, policies, comments) and admin logging utilities for inserting, querying, and aggregating admin action logs.
Discord Integration
backend/integrations/discord/admin_cog.py, backend/integrations/discord/permissions.py, backend/integrations/discord/views.py
New AdminCommands cog with slash commands (stats, health, user_info, user_reset, queue_status, queue_clear, cache_clear); permission decorators and admin logging; ConfirmActionView and onboarding view tweak.
Discord Bot & Tasks
backend/integrations/discord/bot.py, backend/integrations/discord/cogs.py
Switched prints to logger, added global and per-guild command sync flow, and changed task prints to logger.debug.
Application Startup
backend/main.py
Startup now ensures admin_logs table and loads the admin cog, logging outcomes.
Models
backend/app/models/database/weaviate.py
Added WeaviateCodeChunk and WeaviateInteraction Pydantic models and exported them.
Docs & Tests
docs/ADMIN_COMMANDS.md, tests/*_admin*.py, tests/*
Documentation for admin commands and many new/updated tests for monitoring, management, permissions, and integrations; some tests updated to new app.* import paths and runtime path setup.
UTC Timestamping
backend/app/agents/devrel/nodes/gather_context.py, backend/app/database/supabase/services.py, backend/app/services/auth/*.py, backend/app/services/codegraph/repo_service.py, backend/app/services/integration_service.py
Replaced naive datetimes with timezone-aware UTC timestamps across multiple services and DB interactions.
Miscellaneous
backend/app/utils/admin_logger.py, backend/integrations/discord/*, various tests
New admin logging utilities, permission helpers, and confirmation view; multiple test modules and runners added for admin features.

Sequence Diagram(s)

sequenceDiagram
    participant User as Discord User
    participant Bot as Admin Cog
    participant Perm as Permissions
    participant Service as Admin Service
    participant Queue as Queue Manager
    participant DB as Supabase/DB
    participant Logger as Admin Logger

    User->>Bot: /admin stats
    Bot->>Perm: check_permissions(interaction)
    alt permitted
        Perm->>Logger: log_admin_action (initiated)
        Bot->>Service: BotStatsService.get_all_stats()
        Service->>DB: query message stats
        Service->>Queue: query queue stats
        Service->>Service: compute system metrics
        Service-->>Bot: BotStats
        Bot->>Logger: log_admin_action (success)
        Bot-->>User: reply embed with stats
    else denied
        Perm->>Logger: log_admin_action (denied)
        Bot-->>User: ephemeral permission error
    end
Loading
sequenceDiagram
    participant User as Discord User
    participant Bot as Admin Cog
    participant View as ConfirmActionView
    participant Service as UserManagementService
    participant Queue as Queue Manager
    participant DB as Supabase/DB
    participant Logger as Admin Logger

    User->>Bot: /admin user_reset user=@joe reset_memory=true
    Bot->>View: present confirmation
    User->>View: click Confirm
    View-->>Bot: confirmed
    Bot->>Logger: log_admin_action (initiated)
    Bot->>Service: reset_user(reset_memory=True)
    Service->>Queue: enqueue memory cleanup
    Service->>DB: update user verification fields
    Service-->>Bot: ResetResult
    Bot->>Logger: log_admin_action (success)
    Bot-->>User: result embed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • chandansgowda
  • smokeyScraper

Poem

🐰 I count the logs and tidy queues,

I hop through stats and chase bug clues.
With tiny paws I press Confirm,
I nudge the bot to wake and turn.
A carrot for each admin fuse.

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (18 files):

⚔️ .gitignore (content)
⚔️ backend/app/agents/devrel/nodes/gather_context.py (content)
⚔️ backend/app/core/config/settings.py (content)
⚔️ backend/app/database/supabase/services.py (content)
⚔️ backend/app/models/database/weaviate.py (content)
⚔️ backend/app/services/auth/management.py (content)
⚔️ backend/app/services/auth/verification.py (content)
⚔️ backend/app/services/codegraph/repo_service.py (content)
⚔️ backend/app/services/integration_service.py (content)
⚔️ backend/integrations/discord/bot.py (content)
⚔️ backend/integrations/discord/cogs.py (content)
⚔️ backend/integrations/discord/views.py (content)
⚔️ backend/main.py (content)
⚔️ env.example (content)
⚔️ tests/test_embedding_service.py (content)
⚔️ tests/test_supabase.py (content)
⚔️ tests/test_weaviate.py (content)
⚔️ tests/tests_db.py (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ 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 'Feature/add admin commands' clearly and concisely summarizes the main change—adding admin commands to the application.
Linked Issues check ✅ Passed All coding objectives from issue #202 are met: permission-restricted admin Discord slash commands (/admin stats, health, user_info, user_reset, queue_status, queue_clear, cache_clear) with proper authorization checks, confirmation workflows for destructive actions, and admin action logging to database.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing admin commands. Additional timezone fixes and logging improvements are supporting refactorings necessary for the feature.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feature/add-admin-commands
  • Post resolved changes as copyable diffs in a comment

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: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
tests/test_weaviate.py (1)

48-48: ⚠️ Potential issue | 🔴 Critical

Syntax error: extra " creates an unclosed string literal.

"weaviate_user_profile"")" — the second " after the closing quote of the first string opens a new string literal that is never terminated () becomes string content). This will cause a SyntaxError at parse time. The same issue exists on lines 61 and 80.

🐛 Proposed fix for all three occurrences
-        questions = client.collections.get("weaviate_user_profile"")
+        questions = client.collections.get("weaviate_user_profile")
-    questions = get_client().collections.get("weaviate_user_profile"")
+    questions = get_client().collections.get("weaviate_user_profile")
-    questions = get_client().collections.get("weaviate_user_profile"")
+    questions = get_client().collections.get("weaviate_user_profile")
🤖 Fix all issues with AI agents
In @.gitignore:
- Line 111: Remove "poetry.lock" from the ignore list so the lockfile is
committed (or alternatively update the adjacent .gitignore comment to explicitly
justify ignoring it). Specifically, edit the .gitignore entry that currently
contains "poetry.lock" and either delete that line to allow committing the
lockfile (recommended for reproducible deployments) or replace/update the
comment block above to explain the deliberate decision and risks of ignoring
"poetry.lock".

In `@backend/app/services/admin/health_check_service.py`:
- Around line 88-103: The check_falkordb() function is making an HTTP GET to a
Redis port which is invalid; replace the aiohttp call with a Redis protocol
check using the project's FalkorDB client or redis.asyncio: connect with an
asyncio-aware Redis client, send a PING (or use the FalkorDB client's
health/ping method) and await the response, measure latency the same way as
start = datetime.now(), validate the ping reply (e.g., "PONG") and return
ServiceHealth("FalkorDB", "healthy", round(latency,2)) on success, return
degraded with timeout or error details on asyncio.TimeoutError or other
Exceptions (include trimmed error text as in the existing code), and ensure you
use the same timeout semantics (self.timeout) and maintain the ServiceHealth
shape.

In `@backend/app/services/admin/user_info_service.py`:
- Around line 43-52: get_user_message_count (and similarly get_last_message) is
querying a non-existent message_logs table and passing a Discord snowflake
string as user_id; instead, query the interactions table using the internal user
UUID from the users table. Change the logic in
get_user_message_count/get_last_message to either 1) lookup the user's UUID by
querying users where discord_id == discord_id and use that UUID to filter
interactions.user_id, or 2) perform a join between interactions and users (join
users.id == interactions.user_id) and filter users.discord_id == discord_id,
then count/select from interactions; continue using get_supabase_client and
return res.count or 0 but ensure you use the interactions table and the
users.discord_id -> users.id mapping.

In `@backend/app/services/admin/user_management_service.py`:
- Around line 68-82: The timestamp used in reset_verification is naive
(datetime.now())—update the call in the reset_verification method to produce an
aware UTC timestamp (e.g., use datetime.now(timezone.utc).isoformat() or
equivalent) and add the necessary import for timezone from datetime; ensure the
updated_at value sent to supabase.table("users").update(...) is timezone-aware
UTC.

In `@backend/app/utils/admin_logger.py`:
- Around line 165-181: get_admin_log_stats currently builds a
supabase.table("admin_logs").select("*") query and calls response = await
query.execute() with no limit, which can return an unbounded result set; update
get_admin_log_stats to avoid SELECT * without bounds by either adding a
reasonable default limit (e.g., .limit(n)) and exposing paging params, or
implement server-side pagination by looping with .range(offset,
offset+batchSize-1) / .limit and aggregating results incrementally, or
preferably push aggregation into a DB function/view and call that instead;
locate the query construction in get_admin_log_stats (the
supabase.table("admin_logs").select("*") block) and change it to use one of
these approaches and handle multiple pages until completion or return aggregated
results.
- Around line 58-71: The timestamp for log entries is created with
datetime.now() which yields a naive local time; update the code that builds
log_entry (in admin_logger.py, e.g., inside the log_admin_action flow where
log_entry is defined) to produce an aware UTC timestamp such as
datetime.now(timezone.utc). Also add the necessary import (timezone) from
datetime or alternatively use datetime.utcnow() and convert to ISO with a 'Z' or
timezone-aware object so stored timestamps match the DB TIMESTAMPTZ behavior and
comparisons in get_admin_logs are consistent.
- Around line 13-32: The current ensure_admin_logs_table() caches a False result
in _admin_logs_table_ready which permanently disables DB writes; change the
logic so only a successful check sets _admin_logs_table_ready = True and do not
persistently set it to False (or implement a short TTL/backoff instead) so
subsequent calls (e.g., from log_admin_action) will retry table existence
checks; update ensure_admin_logs_table() to omit assigning False on exception
(or add a timestamped cache and re-check after X seconds) and ensure callers
like log_admin_action rely on the function’s dynamic result rather than a
permanently-false flag.

In `@backend/database/02_create_admin_logs_table.sql`:
- Around line 38-42: The INSERT RLS policy "Service role can insert admin logs"
currently allows auth.role() = 'authenticated', which lets any authenticated
user write audit entries; change the policy on table admin_logs (policy name
"Service role can insert admin logs") to only permit auth.role() =
'service_role' by removing the 'authenticated' clause so the WITH CHECK uses
auth.role() = 'service_role' exclusively.

In `@backend/integrations/discord/admin_cog.py`:
- Around line 76-93: The error handler is named cog_command_error which only
catches prefix commands; rename it to cog_app_command_error so it handles
application/slash command errors; update the function definition and docstring
from async def cog_command_error(self, interaction: Interaction, error:
Exception): to async def cog_app_command_error(self, interaction: Interaction,
error: Exception): and leave the existing logic (checks for
app_commands.MissingPermissions and app_commands.CommandInvokeError, usage of
interaction.response.is_done(), await interaction.followup.send(...) and await
interaction.response.send_message(...), and logger.error calls) unchanged so
slash-command exceptions are properly caught and reported.

In `@backend/integrations/discord/bot.py`:
- Around line 40-53: The code uses self.tree.sync() which is not provided by
py-cord; replace those calls with py-cord compatible command syncing (e.g., call
await self.sync_commands() for global sync and await
self.sync_commands(guild=guild) for per-guild sync) or enable auto-sync via
auto_sync_commands=True when constructing the Bot; update the try/except blocks
around the existing sync logic (references: self.tree.sync, guild_synced
variable and the loop over self.guilds) to invoke self.sync_commands(...) so
runtime failures with py-cord are eliminated.

In `@backend/integrations/discord/permissions.py`:
- Around line 240-270: _extract_command_args is incorrectly slicing positional
args with args[2:], which double-skips parameters because the wrapper already
strips self and interaction; update the function to iterate over args (not
args[2:]) when building positional command_args so the first positional
parameter maps to param_names[0]; keep the existing param_names filtering
(removing 'self' and 'interaction') and the existing JSON-serializable
conversion logic so keyword handling remains unchanged.

In `@tests/test_embedding_service.py`:
- Around line 1-10: The test imports
app.services.embedding_service.service.EmbeddingService before the test modifies
sys.path, causing ModuleNotFoundError when run directly; to fix, move the
sys.path insertions (the ROOT and ROOT/"backend" inserts) above the line that
imports EmbeddingService (and any other app.* imports) so path setup occurs
first, or alternatively delay importing EmbeddingService by importing it inside
the test functions/class after sys.path setup; update
tests/test_embedding_service.py (and the same pattern in other tests) to ensure
sys.path modifications happen prior to importing EmbeddingService.

In `@tests/test_supabase.py`:
- Around line 1-10: The file currently imports app modules (get_supabase_client
and User/Interaction/CodeChunk/Repository) before the test adjusts sys.path,
causing ModuleNotFoundError; fix by moving the ROOT and sys.path.insert(0, ...)
setup lines so they appear before any app.* imports (ensure ROOT is defined
first), then import get_supabase_client and the model classes (User,
Interaction, CodeChunk, Repository) after the path setup so the module
resolution succeeds.

In `@tests/test_weaviate.py`:
- Around line 1-12: The test imports (WeaviateUserProfile from
app.models.database.weaviate and get_client from app.database.weaviate.client)
occur before sys.path is adjusted, causing import failures; move the ROOT and
sys.path.insert(0, ...) setup (the Path resolution and both sys.path.insert
calls that reference ROOT) to the top of tests/test_weaviate.py before any app.*
imports so that WeaviateUserProfile and get_client are imported after the path
is configured.
- Around line 1-3: Tests reference two missing model classes: WeaviateCodeChunk
and WeaviateInteraction; add them to the weaviate models module following the
existing patterns used by WeaviateUserProfile, WeaviateRepository, and
WeaviatePullRequest. Implement both classes in
backend/app/models/database/weaviate.py with the same dataclass/ORM structure
and fields consistent with how they are used in tests (ensure
serialization/typing matches existing models), then export/import them where the
other models are exported so tests can import WeaviateCodeChunk and
WeaviateInteraction without NameError.

In `@tests/tests_db.py`:
- Around line 1-10: The import order is wrong: move the sys.path setup (the ROOT
calculation and sys.path.insert calls that add ROOT and ROOT/"backend") above
any imports from app so that "from app.services.vector_db.service import
EmbeddingItem, VectorDBService" runs after the path is updated; locate these
symbols in tests/tests_db.py and reorder so the Path/sys.path insert lines
precede the app.* import, keeping the rest of the file unchanged.
🟡 Minor comments (7)
backend/app/services/admin/cache_service.py-48-59 (1)

48-59: ⚠️ Potential issue | 🟡 Minor

Invalid cache_type silently returns an empty dict.

If a caller passes a typo like "acive_threads", clear_cache returns {} with no indication of an error. Consider validating the input and raising ValueError for unrecognized types.

Proposed fix
     async def clear_cache(self, cache_type: str = "all") -> Dict[str, int]:
+        valid_types = {"all", "active_threads", "embeddings", "memories"}
+        if cache_type not in valid_types:
+            raise ValueError(f"Invalid cache_type '{cache_type}'. Must be one of {valid_types}")
+
         cleared = {}
backend/integrations/discord/views.py-76-78 (1)

76-78: ⚠️ Potential issue | 🟡 Minor

on_timeout disables items but never edits the message, so buttons remain visually active.

Disabling self.children without calling message.edit(view=self) has no visible effect for the user. They'll still see clickable buttons (though callbacks won't fire since the view stopped).

Proposed fix
     async def on_timeout(self):
         for item in self.children:
             item.disabled = True
+        if self.message:
+            try:
+                await self.message.edit(view=self)
+            except Exception:
+                pass

Note: You'll need to ensure self.message is set when sending the view (e.g., by assigning the message returned from send to view.message, or by using await interaction.followup.send(..., view=view) which may set it automatically depending on the py-cord version).

backend/app/services/admin/bot_stats_service.py-37-43 (1)

37-43: ⚠️ Potential issue | 🟡 Minor

Naive datetime.now() may cause incorrect message counts if Supabase stores UTC timestamps.

today_start and week_start are computed with local time, but Supabase created_at columns typically store UTC. This can over- or under-count messages depending on the server's timezone offset.

Consider using datetime.now(timezone.utc) (from datetime import timezone) to ensure consistent comparisons:

Proposed fix
-            now = datetime.now()
+            now = datetime.now(timezone.utc)

And update the import:

-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
docs/ADMIN_COMMANDS.md-227-233 (1)

227-233: ⚠️ Potential issue | 🟡 Minor

FalkorDB missing from the Health Check Failures troubleshooting section.

The /admin health command checks FalkorDB (documented on line 76), but the troubleshooting section doesn't include guidance for FalkorDB failures.

Proposed addition
 - **Weaviate**: Check Docker container and port 8080
+- **FalkorDB**: Verify Docker container is running and port 6379 is accessible
 - **Gemini API**: Verify API key is valid
backend/integrations/discord/admin_cog.py-206-272 (1)

206-272: ⚠️ Potential issue | 🟡 Minor

Early-return at Line 224 sends a new response before @require_admin can log the outcome.

When no reset option is selected, the command replies and returns None. The require_admin wrapper then logs this as a "success" action, even though nothing was actually done. Consider this a minor semantic gap in the audit trail — the logged "success" is for a no-op validation rejection.

backend/app/services/admin/user_management_service.py-45-51 (1)

45-51: ⚠️ Potential issue | 🟡 Minor

close_user_thread returns False when bot is None, causing a misleading "Failed to close thread" error in reset_user.

If the bot reference isn't available, thread management isn't applicable — returning True (or handling it distinctly) would avoid a false-negative error in the aggregated result.

Proposed fix
     async def close_user_thread(self, user_id: str) -> bool:
         try:
             if not self.bot:
-                return False
+                logger.info("Bot not available — skipping thread close")
+                return True
 
             if user_id not in self.bot.active_threads:
                 return True
tests/test_admin_management.py-10-27 (1)

10-27: ⚠️ Potential issue | 🟡 Minor

test_queue_status patches the method under test — it only validates the mock, not real behavior.

patch.object(service, 'get_queue_stats', ...) replaces the very method being tested, so the assertions just confirm the mock's return value. To test real logic, provide a mocked queue_manager with a mocked channel (like test_queue_clear does) and let the real get_queue_stats execute.

Proposed fix: test against real service logic
 async def test_queue_status():
     from app.services.admin.queue_service import QueueService, FullQueueStatus, QueueStats
+    from app.core.orchestration.queue_manager import QueuePriority
+    from unittest.mock import Mock, AsyncMock
 
-    service = QueueService(None)
+    mock_channel = Mock()
+    mock_queue_manager = Mock()
+    mock_queue_manager.channel = mock_channel
+    mock_queue_manager.queues = {
+        QueuePriority.HIGH: "high_task_queue",
+        QueuePriority.MEDIUM: "medium_task_queue",
+        QueuePriority.LOW: "low_task_queue",
+    }
 
-    with patch.object(service, 'get_queue_stats', new_callable=AsyncMock) as mock_stats:
-        mock_stats.return_value = FullQueueStatus(
-            high=QueueStats("high", 5, 2),
-            medium=QueueStats("medium", 10, 3),
-            low=QueueStats("low", 3, 1),
-            total_pending=18
-        )
+    mock_queue = Mock()
+    mock_queue.declaration_result = Mock()
+    mock_queue.declaration_result.message_count = 5
+    mock_queue.declaration_result.consumer_count = 2
+    mock_channel.declare_queue = AsyncMock(return_value=mock_queue)
 
-        status = await service.get_queue_stats()
+    service = QueueService(mock_queue_manager)
+    status = await service.get_queue_stats()
 
-        assert status.total_pending == 18
-        assert status.high.pending == 5
-        print("PASS: test_queue_status")
+    assert status.total_pending == 15  # 5 * 3 queues
+    assert status.high.pending == 5
+    print("PASS: test_queue_status")
🧹 Nitpick comments (12)
backend/integrations/discord/bot.py (2)

38-39: Duplicate log on startup.

Lines 38 and 39 both log that the bot is ready/logged in. One of them should be removed.

-        logger.info(f'Enhanced Discord bot logged in as {self.user}')
-        logger.info(f'Bot is ready! Logged in as {self.user}')
+        logger.info(f'Bot is ready! Logged in as {self.user}')

46-53: Per-guild sync may hit rate limits; outer handler should preserve traceback.

Two observations:

  1. Rate limits: If the bot is in many guilds, sequential tree.sync(guild=guild) calls with no delay can trigger Discord API rate limits. Consider adding a small asyncio.sleep between iterations or only syncing to specific guilds (e.g., a configured allowlist) rather than all.

  2. Line 53: Per static analysis (TRY400), use logger.exception instead of logger.error to capture the full traceback for the global sync failure — this is the more critical failure path.

Suggested fix for traceback logging
         except Exception as e:
-            logger.error(f"Failed to sync slash commands: {e}")
+            logger.exception(f"Failed to sync slash commands: {e}")
backend/app/services/admin/cache_service.py (1)

8-12: CacheInfo dataclass is defined but never used.

It's not referenced anywhere in this file or (based on the summary) by consumers. If it's intended for future use, consider removing it until needed to keep the module lean, or wire it into the return types of the service methods.

backend/app/services/admin/queue_service.py (1)

30-60: Duplicate queue-stats retrieval logic across services.

get_queue_stats here closely mirrors the implementation in BotStatsService.get_queue_stats (see backend/app/services/admin/bot_stats_service.py, lines 56–73). Both iterate self.queue_manager.queues, declare queues passively, and read message_count. Consider extracting the shared queue-inspection logic into a helper to avoid drift between the two implementations.

backend/main.py (1)

60-64: Inconsistent exception handling compared to the existing cog loading pattern.

Line 57 catches (ImportError, commands.ExtensionError) specifically, but the admin cog loading catches bare Exception. Consider aligning for consistency and to avoid silently swallowing unexpected errors.

Proposed fix
             try:
                 await self.discord_bot.load_extension("integrations.discord.admin_cog")
                 logger.info("Admin cog loaded successfully")
-            except Exception as e:
+            except (ImportError, commands.ExtensionError) as e:
                 logger.error("Failed to load admin cog extension: %s", e, exc_info=True)
tests/test_admin_permissions.py (2)

195-231: Consider using pytest with pytest-asyncio instead of a custom test runner.

The hand-rolled run_all_tests orchestrator is repeated across multiple test files and won't integrate with standard CI tooling (pytest discovery, coverage, JUnit reporting). Adopting pytest + pytest-asyncio would eliminate the boilerplate run_all_tests function, the manual pass/fail counting, and the if __name__ == "__main__" block across all test files.

This applies to test_admin_integration.py and test_admin_monitoring.py as well.


7-9: Fragile sys.path manipulation.

Hardcoding relative sys.path.insert assumes a fixed directory layout. If tests are run from a different working directory or the repo structure changes, imports will break. A proper package setup (e.g., pyproject.toml with [tool.pytest.ini_options] pythonpath) or conftest.py would be more robust.

tests/test_admin_integration.py (1)

62-103: Permission tests duplicate those in test_admin_permissions.py.

test_permission_denies_regular_user and test_permission_allows_admin here replicate the same logic already covered in test_admin_permissions.py (test_admin_decorator_denies_regular_user and test_admin_decorator_allows_administrator). Consider removing the duplicates or extracting a shared helper (like the existing create_mock_interaction in test_admin_permissions.py) to reduce maintenance burden.

tests/test_admin_management.py (1)

6-8: sys.path manipulation is fragile — consider adopting pytest with proper package configuration.

Manually inserting paths makes imports dependent on the working directory and can break when the repo layout changes. Using pytest with pyproject.toml / conftest.py or PYTHONPATH in CI is more robust and discoverable. This is consistent across multiple test files in this PR, so it can be addressed holistically.

backend/app/services/admin/health_check_service.py (1)

105-125: Gemini API key is passed as a URL query parameter.

While functional, API keys in URL query strings can be captured by proxy logs, browser history, and CDN/WAF access logs. For a server-side health check this is lower risk, but consider using a request header instead if the API supports it.

backend/integrations/discord/permissions.py (1)

42-133: require_admin and require_bot_owner share ~90% of their logic — consider extracting a shared helper.

The two decorators differ only in the permission check and some metadata fields. A common _make_permission_decorator(check_fn, ...) would eliminate the duplication and make future changes (e.g., adding a new log field) less error-prone.

Also applies to: 136-227

backend/integrations/discord/admin_cog.py (1)

293-294: Redundant color assignment — embed is created with Color.blue() and immediately overwritten.

Proposed fix
-            embed = discord.Embed(title="Queue Status", color=discord.Color.blue())
-            embed.color = color
+            embed = discord.Embed(title="Queue Status", color=color)

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.

Caution

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

⚠️ Outside diff range comments (2)
tests/test_weaviate.py (2)

50-50: ⚠️ Potential issue | 🔴 Critical

Syntax error: extra closing quote.

"weaviate_user_profile"" has a stray " at the end, making this a syntax error. The same issue appears on lines 63 and 82. The file won't parse at all until these are fixed.

-        questions = client.collections.get("weaviate_user_profile"")
+        questions = client.collections.get("weaviate_user_profile")

Apply the same fix on lines 63 and 82.


26-44: ⚠️ Potential issue | 🟠 Major

WeaviateUserProfile constructed with non-existent fields.

The fields used here (supabase_user_id, profile_summary, primary_languages, expertise_areas, embedding) don't exist on WeaviateUserProfile, which expects user_id, github_username, profile_text_for_embedding, etc. This will raise a Pydantic ValidationError at runtime. Since the import path was updated to point at the new model module, this test function needs to be updated to match the actual model schema.

🧹 Nitpick comments (2)
backend/app/models/database/weaviate.py (1)

157-163: Nit: __all__ is not sorted.

Per the Ruff RUF022 hint, consider sorting the entries alphabetically for consistency.

Suggested fix
 __all__ = [
+    "WeaviateCodeChunk",
+    "WeaviateInteraction",
+    "WeaviatePullRequest",
     "WeaviateRepository",
-    "WeaviatePullRequest",
-    "WeaviateCodeChunk",
-    "WeaviateInteraction",
     "WeaviateUserProfile",
 ]
tests/test_weaviate.py (1)

103-121: Pre-existing: uses deprecated .dict() instead of .model_dump().

Lines 114, 37, and 188 all use .dict(by_alias=True) which is deprecated in Pydantic v2. The project uses Pydantic v2 (ConfigDict, model_config), so these should be .model_dump(by_alias=True).

@SiddhantGahankari SiddhantGahankari marked this pull request as draft February 16, 2026 10:29
@SiddhantGahankari SiddhantGahankari marked this pull request as ready for review February 16, 2026 10:35
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

Caution

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

⚠️ Outside diff range comments (1)
backend/app/agents/devrel/nodes/gather_context.py (1)

78-78: ⚠️ Potential issue | 🟡 Minor

last_interaction_time still uses naive datetime.now() — inconsistent with the UTC migration.

Line 78 and Line 93 both set last_interaction_time with datetime.now() (naive), while new_message["timestamp"] on Line 30 was correctly updated to datetime.now(timezone.utc). This creates a mix of timezone-aware and naive datetimes in the same return dict, which can cause TypeError on comparison and defeats the purpose of this PR's UTC migration.

Proposed fix
             return {
                 "messages": [new_message],
                 "context": {**state.context, **context_data},
                 "conversation_summary": prev_context.get("conversation_summary"),
                 "key_topics": prev_context.get("key_topics", []),
                 "current_task": "context_gathered",
-                "last_interaction_time": datetime.now()
+                "last_interaction_time": datetime.now(timezone.utc)
             }

And similarly on Line 93:

     result: Dict[str, Any] = {
         "messages": [new_message],
         "context": updated_context,
         "current_task": "context_gathered",
-        "last_interaction_time": datetime.now(),
+        "last_interaction_time": datetime.now(timezone.utc),
     }
🤖 Fix all issues with AI agents
In `@backend/app/services/admin/user_info_service.py`:
- Around line 92-95: The has_active_thread method can raise AttributeError when
self.bot exists but lacks active_threads; update has_active_thread to safely
check for the attribute (e.g., using getattr or hasattr) on self.bot before
membership testing, and return False when either self.bot is falsy or
active_threads is missing, referencing the has_active_thread method and the
self.bot.active_threads attribute in your fix.

In `@backend/integrations/discord/admin_cog.py`:
- Around line 360-407: The handler cache_clear currently only prompts
confirmation for cache_type == "all"; update cache_clear so any destructive
cache clear (i.e., any cache_type) requires confirmation by calling
self._confirm_action for non-empty cache_type (use cache_type to customize the
title/description, e.g., "Confirm Cache Clear - {cache_type}") before calling
self.cache_service.clear_cache; keep the existing defer/response flow (use
interaction.response.defer for the non-confirmed branch as currently done), and
ensure the followup/edit_original_response logic and exception handling remain
unchanged.
🧹 Nitpick comments (9)
backend/integrations/discord/bot.py (2)

38-39: Duplicate "ready" log messages.

Line 38 and 39 both log that the bot is ready/logged in. One of these is redundant.

Suggested fix
-        logger.info(f'Enhanced Discord bot logged in as {self.user}')
-        logger.info(f'Bot is ready! Logged in as {self.user}')
+        logger.info(f'Bot is ready! Logged in as {self.user}')

54-55: Use logger.exception to preserve the stack trace.

When the global sync fails, the traceback is lost with logger.error. Using logger.exception (or logger.error(..., exc_info=True)) will include the full traceback, which is valuable for diagnosing startup failures.

Suggested fix
         except Exception as e:
-            logger.error(f"Failed to sync slash commands: {e}")
+            logger.exception(f"Failed to sync slash commands: {e}")
backend/app/services/admin/health_check_service.py (2)

148-163: Failed gather results lose the service name — consider mapping by index.

When a check raises (instead of returning a ServiceHealth), it's replaced with ServiceHealth("Unknown", ...). Since asyncio.gather preserves input order, you can recover the service name from the index.

Suggested improvement
     async def get_all_health(self) -> SystemHealth:
+        check_names = ["Supabase", "RabbitMQ", "Weaviate", "FalkorDB", "Gemini API"]
         checks = await asyncio.gather(
             self.check_supabase(),
             self.check_rabbitmq(),
             self.check_weaviate(),
             self.check_falkordb(),
             self.check_gemini_api(),
             return_exceptions=True
         )
 
         services = []
-        for check in checks:
+        for i, check in enumerate(checks):
             if isinstance(check, Exception):
-                services.append(ServiceHealth("Unknown", "unhealthy", 0, str(check)[:100]))
+                name = check_names[i] if i < len(check_names) else "Unknown"
+                services.append(ServiceHealth(name, "unhealthy", 0, str(check)[:100]))
             else:
                 services.append(check)

126-146: Gemini API key sent as a query parameter — standard but worth noting.

Passing the key via params={"key": ...} is the documented Google API pattern, but be aware that query-string parameters can appear in proxy/CDN/access logs. If the project later adds request-level logging or an HTTP-tracing middleware, ensure API keys are redacted.

backend/app/services/admin/user_info_service.py (1)

97-127: _get_internal_user_id is called twice — once per get_user_message_count and get_last_message.

get_full_user_info invokes both get_user_message_count and get_last_message, each resolving the internal user ID independently. This doubles the lookup. Consider resolving the ID once and passing it down, or adding a lightweight internal helper that accepts an optional pre-resolved ID.

backend/app/services/admin/user_management_service.py (1)

26-43: Memory clear is fire-and-forget — consider noting this in the return semantics.

clear_user_memory returns True as soon as the message is enqueued, not when the memory is actually cleared. This matches the existing pattern in cogs.py (reset_thread), but callers (and the ResetResult.memory_cleared field) may mislead admins into thinking the operation completed. A docstring clarifying "queued, not confirmed" would help.

backend/database/02_create_admin_logs_table.sql (1)

16-26: Redundant single-column indexes alongside composite indexes.

idx_admin_logs_executor_id (line 18) is redundant with idx_admin_logs_executor_timestamp (line 25), and idx_admin_logs_command_name (line 19) is redundant with idx_admin_logs_command_timestamp (line 26). PostgreSQL can use the leading column of a composite index for equality lookups. Dropping the single-column indexes saves storage and write overhead on an append-heavy audit table.

Proposed cleanup
 CREATE INDEX IF NOT EXISTS idx_admin_logs_timestamp ON admin_logs(timestamp DESC);
-CREATE INDEX IF NOT EXISTS idx_admin_logs_executor_id ON admin_logs(executor_id);
-CREATE INDEX IF NOT EXISTS idx_admin_logs_command_name ON admin_logs(command_name);
 CREATE INDEX IF NOT EXISTS idx_admin_logs_action_result ON admin_logs(action_result);
 CREATE INDEX IF NOT EXISTS idx_admin_logs_server_id ON admin_logs(server_id);
 CREATE INDEX IF NOT EXISTS idx_admin_logs_target_id ON admin_logs(target_id) WHERE target_id IS NOT NULL;
backend/integrations/discord/permissions.py (1)

42-133: require_admin and require_bot_owner share ~90% identical code — consider extracting a shared helper.

The two decorators differ only in the permission check and a couple of metadata fields. The logging, argument extraction, command execution, and error-handling logic is duplicated verbatim. Extracting a common _permission_decorator(check_fn, ...) would reduce maintenance burden and the risk of the two drifting out of sync.

Also applies to: 136-227

backend/integrations/discord/admin_cog.py (1)

292-294: Redundant initial color on embed.

Line 293 sets color=discord.Color.blue(), then line 294 immediately overrides it with the computed color. Drop the initial value.

Proposed fix
-            embed = discord.Embed(title="Queue Status", color=discord.Color.blue())
-            embed.color = color
+            embed = discord.Embed(title="Queue Status", color=color)

Comment on lines +92 to +95
def has_active_thread(self, discord_id: str) -> bool:
if not self.bot:
return False
return discord_id in self.bot.active_threads
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

self.bot.active_threads may raise AttributeError if the attribute doesn't exist on the bot.

If the bot instance doesn't have an active_threads attribute, this will throw instead of returning False.

Proposed fix
     def has_active_thread(self, discord_id: str) -> bool:
         if not self.bot:
             return False
-        return discord_id in self.bot.active_threads
+        active_threads = getattr(self.bot, 'active_threads', {})
+        return discord_id in active_threads
🤖 Prompt for AI Agents
In `@backend/app/services/admin/user_info_service.py` around lines 92 - 95, The
has_active_thread method can raise AttributeError when self.bot exists but lacks
active_threads; update has_active_thread to safely check for the attribute
(e.g., using getattr or hasattr) on self.bot before membership testing, and
return False when either self.bot is falsy or active_threads is missing,
referencing the has_active_thread method and the self.bot.active_threads
attribute in your fix.

Comment on lines +360 to +407
@app_commands.command(name="cache_clear", description="Clear cached data")
@app_commands.describe(cache_type="What to clear")
@app_commands.choices(cache_type=[
app_commands.Choice(name="All", value="all"),
app_commands.Choice(name="Active Threads", value="active_threads"),
app_commands.Choice(name="Embeddings", value="embeddings"),
app_commands.Choice(name="Memories", value="memories")
])
@require_admin
async def cache_clear(self, interaction: Interaction, cache_type: str = "all"):
"""Clear various caches."""
try:
if cache_type == "all":
confirmed = await self._confirm_action(
interaction,
title="Confirm Cache Clear",
description=(
"You are about to clear **all** caches.\n"
"This action may affect active sessions and performance."
),
)
if not confirmed:
return
else:
await interaction.response.defer(ephemeral=True)

cleared = await self.cache_service.clear_cache(cache_type=cache_type)

embed = discord.Embed(
title="Cache Clear",
description=f"Cleared cache type: {cache_type}",
color=discord.Color.blue(),
)
for name, count in cleared.items():
embed.add_field(name=name.replace("_", " ").title(), value=str(count), inline=True)

if cache_type == "all":
await interaction.edit_original_response(embed=embed, view=None)
else:
await interaction.followup.send(embed=embed, ephemeral=True)

except Exception as e:
logger.error(f"Cache clear failed: {e}", exc_info=True)
if interaction.response.is_done():
await interaction.followup.send(f"Clear failed: {str(e)}", ephemeral=True)
else:
await interaction.response.send_message(f"Clear failed: {str(e)}", ephemeral=True)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-"all" cache clears skip confirmation — intentional?

The PR objectives state cache_clear is destructive and requires confirmation. Currently, only cache_type="all" triggers the confirmation dialog (line 372); individual types like active_threads or embeddings go straight through. If this is intentional, a brief code comment would clarify the rationale. If not, the confirmation should apply to all cache_type values.

🧰 Tools
🪛 Ruff (0.15.0)

[warning] 404-404: Use explicit conversion flag

Replace with conversion flag

(RUF010)


[warning] 406-406: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In `@backend/integrations/discord/admin_cog.py` around lines 360 - 407, The
handler cache_clear currently only prompts confirmation for cache_type == "all";
update cache_clear so any destructive cache clear (i.e., any cache_type)
requires confirmation by calling self._confirm_action for non-empty cache_type
(use cache_type to customize the title/description, e.g., "Confirm Cache Clear -
{cache_type}") before calling self.cache_service.clear_cache; keep the existing
defer/response flow (use interaction.response.defer for the non-confirmed branch
as currently done), and ensure the followup/edit_original_response logic and
exception handling remain unchanged.

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:Add Admin Commands for Bot Management and Monitoring

1 participant