Skip to content

Conversation

@kzndotsh
Copy link
Contributor

@kzndotsh kzndotsh commented Jan 22, 2026

Pull Request

Description

Provide a clear summary of your changes and reference any related issues. Include the motivation behind these changes and list any new dependencies if applicable.

If your PR is related to an issue, please include the issue number below:

Related Issue: Closes #

Type of Change:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring
  • Test improvements

Guidelines

  • My code follows the style guidelines of this project (formatted with Ruff)

  • I have performed a self-review of my own code

  • I have commented my code, particularly in hard-to-understand areas

  • I have made corresponding changes to the documentation if needed

  • My changes generate no new warnings

  • I have tested this change

  • Any dependent changes have been merged and published in downstream modules

  • I have added all appropriate labels to this PR

  • I have followed all of these guidelines.

How Has This Been Tested? (if applicable)

Please describe how you tested your code. e.g describe what commands you ran, what arguments, and any config stuff (if applicable)

Screenshots (if applicable)

Please add screenshots to help explain your changes.

Additional Information

Please add any other information that is important to this PR.

Summary by Sourcery

Improve interaction handling, permissions, and performance across multiple bot modules while introducing configurable Discord intents and sequence repair tooling.

New Features:

  • Add a db fix-sequences maintenance command to automatically realign PostgreSQL sequences with table IDs.

Enhancements:

  • Refactor multiple commands to consistently handle both slash and prefix invocations with early defers and ephemeral followups.
  • Optimize help and permission systems by batching permission checks and command permission lookups to reduce database load.
  • Introduce configurable Discord gateway intents in configuration and wire them into bot startup.
  • Improve status role checks and activity rotation so they only run heavy operations on the first ready event and batch member processing.
  • Add HTTP client configuration for high-latency environments to better tune Discord REST and gateway behavior.
  • Add a utility for safer user resolution using the cache before REST calls and improve member/user lookup patterns in info-related commands.
  • Harden case creation by ignoring manual ID injection and improve AFK, levels, moderation, and wiki flows for better UX and error messaging.

Build:

  • Bump Python version to 3.13.11 across CI, Docker, pre-commit, and tooling configs.

Deployment:

  • Update container base image to python:3.13.11-slim for production builds.

Documentation:

  • Update docs to reference the new Python base image version and workflow matrix configuration.

Chores:

  • Add a mise.toml specifying Python 3.13.11 for local tooling alignment.
  • Wire a new db fix-sequences CLI subcommand into the scripts entrypoint for easier database maintenance.

- Updated the default Python version from 3.13 to 3.13.11 in the pre-commit configuration file to ensure compatibility with the latest features and improvements.
- Updated the Python version from 3.13.8 to 3.13.11 in multiple GitHub Actions workflows and the setup-python action to ensure consistency and compatibility with the latest features.
- Updated documentation to reflect the change of Python version from 3.13.8 to 3.13.11 across various files, ensuring consistency in the usage of the latest Python version in CI/CD configurations and Docker references.
- Introduced a new command `fix-sequences` that resets PostgreSQL sequences to the maximum ID value in their respective tables, addressing synchronization issues that can lead to duplicate key violations.
- Updated the main application to include the new command, enhancing database management capabilities.
- Updated the Tux bot instance creation to retrieve intents from the configuration, enhancing flexibility in bot behavior based on user-defined settings.
- This change allows for more granular control over the bot's event handling capabilities.
- Introduced a new method `send_after_defer` in the BaseCog class to streamline message sending after deferring, accommodating both slash and prefix commands.
- This method enhances flexibility by allowing for optional content, embeds, and files, while also managing ephemeral messages and author mentions.
- Improved error handling with logging for failed message sends, ensuring better debugging capabilities.
…tency environments

- Introduced a new flag `first_ready` to track the initial on_ready event, improving state management.
- Configured the HTTP client for high-latency environments early in the setup process to enhance performance and reliability during REST calls.
…er resolution

- Introduced the `get_user_safe` function to retrieve users from the bot's cache before making REST API calls, improving performance in high-latency environments.
- The function handles user fetching with error logging for better debugging and reliability.
- Introduced a new module to configure discord.py's HTTP client for optimal performance in high-latency, high-jitter environments.
- Enhanced connection pooling and DNS caching settings, including adjustments to DNS cache TTL and keepalive timeout.
- Implemented error handling and logging for configuration failures, ensuring graceful degradation and visibility into issues.
…ieval

- Refactored the command permission checking logic to support batch retrieval of permissions for multiple commands, improving efficiency.
- Introduced a new method `batch_get_command_permissions` to allow simultaneous permission checks for a list of command names, reducing database query overhead.
- Updated logging to use trace level for parent command permission usage, enhancing clarity during permission checks.
- Updated the case creation logic to filter out the 'id' key from additional kwargs, ensuring that the database auto-generates the ID.
- Added a warning log when 'id' is included in kwargs, enhancing clarity and preventing potential issues with manual ID assignment.
… processing

- Introduced caching for user permission rank to reduce database queries during help operations.
- Added a new method `_filter_commands_for_permission_check` to streamline the filtering of commands based on their permission requirements.
- Enhanced `batch_can_run_commands` to efficiently check permissions for multiple commands, improving performance and reducing overhead.
- Updated documentation for new methods and clarified permission checking logic.
…ocessing

- Enhanced the permission checking logic in the TuxHelp class to utilize batch processing for subcommands, significantly improving performance.
- Replaced individual permission checks with a batch check, reducing overhead and streamlining the command filtering process.
…rocessing

- Improved the permission filtering logic in HelpNavigation by replacing individual permission checks with a batch processing approach, enhancing performance.
- This change allows for faster permission validation for subcommands, streamlining the command filtering process.
- Introduced a new method `_send_embed` to streamline sending embeds for both slash and prefix commands, improving user experience.
- Added validation methods for emoji, threshold, and channel permissions, providing clear error messages through embeds when validation fails.
- Refactored the starboard command logic to utilize the new validation methods, ensuring better error handling and user feedback.
- Improved overall code organization and readability by consolidating validation logic.
- Enhanced the on_ready method to check user statuses only on the first startup, reducing unnecessary REST calls during reconnects.
- Implemented batch processing of members in chunks to improve performance and manage rate limits effectively.
- Added logging to track the processing of members and completion of status role checks for better visibility.
…ion commands

- Added early interaction deferral to acknowledge user commands before executing asynchronous tasks in various moderation commands, enhancing user experience.
- Updated response handling to utilize interaction follow-ups where applicable, ensuring consistent feedback for users across different moderation actions.
…d avatar commands

- Added early interaction deferral to the xkcd and avatar commands to acknowledge user interactions before executing asynchronous tasks, enhancing user experience.
- Updated response handling to utilize interaction follow-ups where applicable, ensuring consistent feedback for users across both comic retrieval and avatar display functionalities.
…ing for level commands

- Added early interaction deferral to level commands to acknowledge user interactions before executing asynchronous tasks, improving user experience.
- Updated response handling to utilize interaction follow-ups where applicable, ensuring consistent feedback for users across level-related commands.
…confirmation handling

- Added methods for error message handling and timeout duration validation to improve user feedback and command reliability.
- Implemented early interaction deferral to acknowledge user commands before executing asynchronous tasks, enhancing user experience.
- Refactored confirmation dialog handling to streamline user interactions and ensure consistent feedback across different command contexts.
… interaction handling

- Introduced new methods for building converters and trying entity resolutions, improving the command's ability to handle various Discord objects.
- Implemented early interaction deferral to acknowledge user commands before executing asynchronous tasks, enhancing user experience.
- Updated response handling to utilize interaction follow-ups for sending error messages and embeds, ensuring consistent feedback across different contexts.
- Refactored existing logic to streamline the command flow and improve code readability.
…ling for AFK, RemindMe, and Wiki commands

- Added early interaction deferral to AFK, RemindMe, and Wiki commands to acknowledge user interactions before executing asynchronous tasks, enhancing user experience.
- Updated response handling to utilize interaction follow-ups where applicable, ensuring consistent feedback for users across different command contexts.
- Added logic to ensure activity rotation starts only on the first ready event, preventing multiple tasks from starting on reconnects.
- Introduced a delay before starting the activity rotation to ensure the bot is fully connected, addressing potential disconnection issues.
…nfiguration

- Introduced a new BotIntents model to manage Discord bot gateway intents, including members, presences, and message content.
- Updated the Config class to include BOT_INTENTS, allowing for easy configuration of bot intents.
- Enhanced organization of configuration models by extracting intents-related settings into a dedicated model.
- Updated the Python version in .python-version and Containerfile to 3.13.11 for consistency.
- Added mise.toml to specify the Python version for tools, ensuring compatibility across environments.
- Updated the Python image reference in the Containerfile to remove the specific SHA256 digest, streamlining the image definition for easier updates in the future.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 22, 2026

Reviewer's Guide

Refactors several Discord bot cogs to better support slash vs prefix behavior and high-latency environments, introduces batched permission checks and a sequence-fix CLI for the database, makes intents configurable, optimizes startup/on_ready handlers, and updates tooling/docs to Python 3.13.11.

Class diagram for permission system batching and help integration

classDiagram
    class PermissionSystem {
        +async get_command_permission(guild_id: int, command_name: str) PermissionCommand|None
        +async batch_get_command_permissions(guild_id: int, command_names: list[str]) dict[str, PermissionCommand|None]
        +async get_user_permission_rank(ctx: commands.Context[Any]) int
    }

    class HelpData {
        -ctx: commands.Context[Any]|None
        -command_mapping: dict[str, dict[str, commands.Command[Any,Any,Any]]]|None
        -_cached_user_rank: int|None
        +async can_run_command(command: commands.Command[Any,Any,Any]) bool
        +async batch_can_run_commands(commands_list: list[commands.Command[Any,Any,Any]]) dict[commands.Command[Any,Any,Any], bool]
        -_filter_commands_for_permission_check(commands_list: list[commands.Command[Any,Any,Any]]) tuple[dict[commands.Command[Any,Any,Any], bool], list[commands.Command[Any,Any,Any]]]
        -_uses_permission_system(command: commands.Command[Any,Any,Any]) bool
        -_is_owner_or_sysadmin() bool
        +find_command(command_name: str) commands.Command[Any,Any,Any]|None
        +find_parent_command(subcommand_name: str) commands.Command[Any,Any,Any]|None
    }

    class HelpNavigation {
        -data: HelpData
        -current_command_obj: commands.Command[Any,Any,Any]|None
        -current_command: str|None
        +async create_command_view() HelpView
    }

    class PermissionCommand {
        +guild_id: int
        +command_name: str
        +required_rank: int
    }

    HelpNavigation --> HelpData : uses
    HelpData --> PermissionSystem : uses
    PermissionSystem --> PermissionCommand : returns
Loading

Class diagram for configurable bot intents and HTTP configuration

classDiagram
    class Config {
        +BOT_INFO: BotInfo
        +BOT_INTENTS: BotIntents
        +USER_IDS: UserIds
        +DATABASE: DatabaseConfig
    }

    class BotIntents {
        +presences: bool
        +members: bool
        +message_content: bool
        +to_discord_intents() discord.Intents
    }

    class App {
        -config: Config
        +_create_bot_instance(owner_ids: set[int]) Tux
    }

    class Tux {
        +http: HTTPClient
        +first_ready: bool
        +guilds_registered: asyncio.Event
        +maintenance_mode: bool
        +guilds: list[discord.Guild]
        +async setup_hook() None
    }

    class HTTPClient {
        -_HTTPClient__session: aiohttp.ClientSession
        +ws_connect(url: str, **kwargs) Any
        +request(method: str, url: str, **kwargs) Any
    }

    class aiohttp.ClientSession {
        +connector: aiohttp.TCPConnector|None
    }

    class aiohttp.TCPConnector {
        +limit: int
        +limit_per_host: int
        +ttl_dns_cache: int
        +keepalive_timeout: float
    }

    class HttpConfigModule {
        +configure_discord_http_client(bot: Tux) None
    }

    Config --> BotIntents : owns
    App --> Config : uses
    App --> Tux : creates
    BotIntents --> "1" discord.Intents : builds
    Tux --> HTTPClient : has
    HTTPClient --> aiohttp.ClientSession : has
    aiohttp.ClientSession --> aiohttp.TCPConnector : has
    HttpConfigModule --> Tux : configures
Loading

Class diagram for new HTTP configuration helper and BaseCog messaging helper

classDiagram
    class BaseCog {
        +bot: Tux
        +db: DatabaseService
        +get_config(key: str, default: Any) Any
        +async send_after_defer(ctx: commands.Context[Tux], content: str|None, embed: discord.Embed|None, embeds: list[discord.Embed]|None, file: discord.File|None, files: list[discord.File]|None, ephemeral: bool, mention_author: bool) discord.Message|None
    }

    class HttpConfigModule {
        +configure_discord_http_client(bot: Tux) None
    }

    class Tux {
        +http: HTTPClient
        +first_ready: bool
        +async setup_hook() None
    }

    class HTTPClient {
        -_HTTPClient__session: aiohttp.ClientSession
    }

    class aiohttp.ClientSession {
        +connector: aiohttp.TCPConnector|None
    }

    class aiohttp.TCPConnector {
        +limit: int
        +limit_per_host: int
        +ttl_dns_cache: int
        +keepalive_timeout: float
    }

    BaseCog <|-- SomeCog
    BaseCog --> Tux : uses
    Tux --> HTTPClient : has
    HTTPClient --> aiohttp.ClientSession : has
    aiohttp.ClientSession --> aiohttp.TCPConnector : has
    HttpConfigModule --> Tux : configures
Loading

Flow diagram for on_ready startup, guild registration, and first_ready gating

flowchart TD
    A_bot_start["Bot start"] --> B_connect["discord.py connects to gateway"]
    B_connect --> C_on_ready_event["EventHandler.on_ready"]

    C_on_ready_event --> D_register_guilds["Register guilds in database"]
    D_register_guilds --> E_mark_registered["_guilds_registered = True"]
    E_mark_registered --> F_set_event["bot.guilds_registered.set()"]
    F_set_event --> G_first_ready_check{bot.first_ready?}

    G_first_ready_check -- "False (first time)" --> H_set_first["bot.first_ready = True"]
    H_set_first --> I_log_first["Log 'First on_ready event completed'"]

    G_first_ready_check -- "True (reconnect)" --> J_skip_first["Skip first_ready changes"]

    %% StatusRoles handler
    C_status_ready["StatusRoles.on_ready"] --> K_maintenance{bot.maintenance_mode?}
    K_maintenance -- "True" --> L_exit_status["Return (skip checks)"]
    K_maintenance -- "False" --> M_first_ready_status{bot.first_ready?}

    M_first_ready_status -- "False (first startup)" --> N_process_members["Process members in chunks per guild"]
    N_process_members --> O_chunk_loop["For each chunk of ~20 non-bot members"]
    O_chunk_loop --> P_gather["asyncio.gather(check_and_update_roles)"]
    P_gather --> Q_sleep["asyncio.sleep(0.1) between chunks"]
    Q_sleep --> O_chunk_loop
    O_chunk_loop --> R_done_status["Completed status role checks"]

    M_first_ready_status -- "True (not first)" --> S_skip_status["Log 'Skipping member check' and return"]

    %% Activity handler
    C_activity_ready["ActivityHandler.on_ready"] --> T_task_check{"_activity_task is None or done?"}
    T_task_check -- "True" --> U_sleep2["asyncio.sleep(2)"]
    U_sleep2 --> V_start_loop["Create task for _activity_loop()"]
    T_task_check -- "False" --> W_skip_activity["Do nothing (task already running)"]

    %% Synchronization via guilds_registered Event
    F_set_event --> C_status_ready
    F_set_event --> C_activity_ready
Loading

Flow diagram for db fix-sequences CLI command

flowchart TD
    A_cmd["User runs: scripts.db fix-sequences"] --> B_typer_entry["fix_sequences() (Typer command)"]
    B_typer_entry --> C_print_intro["Print intro and dry-run notice"]
    C_print_intro --> D_run_async["asyncio.run(_fix_sequences(dry_run))"]

    D_run_async --> E_create_service["Create DatabaseService(echo=False)"]
    E_create_service --> F_connect_db["connect(CONFIG.database_url)"]
    F_connect_db --> G_get_sequences["execute_query(_get_sequences, 'get_sequences')"]

    G_get_sequences --> H_any_sequences{Any sequences found?}
    H_any_sequences -- "No" --> I_print_none["Print 'No sequences found' and return"]
    H_any_sequences -- "Yes" --> J_iterate["Loop over each sequence"]

    J_iterate --> K_check_seq["execute_query(_check_and_fix_sequence, 'check_sequence')"]
    K_check_seq --> L_discord_check{"max_id >= DISCORD_SNOWFLAKE_MIN?"}

    L_discord_check -- "Yes" --> M_skip_discord["Print skip message (Discord ID)" ]
    M_skip_discord --> J_iterate

    L_discord_check -- "No" --> N_needs_fix{"next_value <= max_id?"}
    N_needs_fix -- "No" --> O_in_sync["Print sequence in sync"]
    O_in_sync --> J_iterate

    N_needs_fix -- "Yes" --> P_compute_new["new_value = max(max_id, 1)"]
    P_compute_new --> Q_dry_run{dry_run?}

    Q_dry_run -- "True" --> R_print_dry["Print '[DRY RUN] Would reset' message"]
    R_print_dry --> J_iterate

    Q_dry_run -- "False" --> S_reset_call["execute_query(_reset_sequence, 'reset_sequence')"]
    S_reset_call --> T_commit["setval(sequence, new_value, true) and commit"]
    T_commit --> U_inc_counter["Increment fixes_applied and print success for sequence"]
    U_inc_counter --> J_iterate

    J_iterate --> V_done_all["Loop finished"]
    V_done_all --> W_summary{fixes_applied > 0?}

    W_summary -- "Yes" --> X_print_fixed["print_success('Fixed N sequence(s)')"]
    W_summary -- "No and dry_run" --> Y_print_dry_all["Print 'All sequences are in sync (dry run)' "]
    W_summary -- "No and not dry_run" --> Z_print_ok["print_success('All sequences are already in sync')"]

    X_print_fixed --> AA_disconnect["disconnect()"]
    Y_print_dry_all --> AA_disconnect
    Z_print_ok --> AA_disconnect

    AA_disconnect --> AB_exit["Return from CLI"]
Loading

File-Level Changes

Change Details Files
Refactor self-timeout and AFK-related moderation flows to support interactions cleanly and reduce duplication.
  • Extract helpers for error messaging, duration validation, confirmation dialogs, DM notifications, and applying timeouts in self_timeout cog
  • Unify AFK clear and AFK commands to send via interaction followups when applicable
  • Ensure early ctx.defer(ephemeral=True) and correct ephemeral behavior in moderation flows involving AFK and self-timeouts
src/tux/modules/utility/self_timeout.py
src/tux/modules/utility/afk.py
src/tux/modules/moderation/clearafk.py
Improve hybrid command handling and interaction support across info, starboard, xkcd, levels, avatar, wiki, remindme and moderation cogs.
  • Add helpers to route responses between ctx.interaction.followup and ctx.send/ctx.reply, ensuring ephemeral messages for slash commands
  • Defer interactions early in many commands (cases, levels, polls, snippets, moderation actions, xkcd, wiki, remindme, avatar, level, afk) to avoid timeouts
  • Adjust embed sending and error paths to respect ephemeral and interaction contexts consistently
src/tux/modules/info/info.py
src/tux/modules/features/starboard.py
src/tux/modules/fun/xkcd.py
src/tux/modules/levels/levels.py
src/tux/modules/info/avatar.py
src/tux/modules/utility/wiki.py
src/tux/modules/utility/remindme.py
src/tux/modules/moderation/cases.py
src/tux/modules/moderation/pollban.py
src/tux/modules/moderation/pollunban.py
src/tux/modules/moderation/snippetban.py
src/tux/modules/moderation/snippetunban.py
src/tux/modules/moderation/ban.py
src/tux/modules/moderation/kick.py
src/tux/modules/moderation/tempban.py
src/tux/modules/moderation/timeout.py
src/tux/modules/moderation/untimeout.py
src/tux/modules/moderation/warn.py
src/tux/core/base_cog.py
Optimize help/permission system by batching permission checks and database queries.
  • Introduce batch_can_run_commands in HelpData with caching of user rank and filtering logic for commands needing checks
  • Add batch_get_command_permissions to PermissionSystem to resolve command and parent permissions in a single query
  • Refactor help navigation and help command to use batched permission checks for subcommands instead of per-command checks
src/tux/help/data.py
src/tux/core/permission_system.py
src/tux/help/navigation.py
src/tux/help/help.py
Improve startup/on_ready behavior and HTTP client configuration for high-latency environments.
  • Add bot.first_ready flag and use it in StatusRoles and Event handlers to only perform heavy member scans once
  • Batch StatusRoles member processing with asyncio.gather, chunking, and sleeps to reduce rate-limit pressure
  • Introduce http_config.configure_discord_http_client and call it from Tux.setup_hook to tune aiohttp connector properties where possible
  • Ensure activity handler only starts rotation once and waits briefly before starting the loop
src/tux/modules/features/status_roles.py
src/tux/services/handlers/event.py
src/tux/services/handlers/activity.py
src/tux/core/bot.py
src/tux/core/http_config.py
Make Discord intents configurable via settings and wire them into bot creation.
  • Add BotIntents model with presences, members, and message_content flags plus to_discord_intents helper
  • Expose BOT_INTENTS in global config and use it in app._create_bot_instance instead of Intents.all()
  • Update related config typing imports for discord when type-checking
src/tux/shared/config/models.py
src/tux/shared/config/settings.py
src/tux/core/app.py
Harden database case creation and add a CLI to repair out-of-sync PostgreSQL sequences.
  • Filter out id from case creation kwargs to prevent manual ID injection and log when ignored
  • Add scripts.db.fix_sequences command that discovers sequences, compares them with table max IDs, and optionally resets them (with dry-run mode), skipping Discord snowflake-based IDs
  • Register fix_sequences in db CLI entrypoint
src/tux/database/controllers/case.py
scripts/db/fix_sequences.py
scripts/db/__init__.py
Add utility for safe user fetching and optimize user-related info flows.
  • Add get_user_safe helper that checks cache before REST fetch and handles errors
  • Switch member info to use member.user for banner instead of fetch_user to avoid extra HTTP calls
src/tux/core/converters.py
src/tux/modules/info/info.py
Make various embeds/outputs interaction-aware and more robust in moderation and info flows.
  • Adjust guild, member, user, channel, role, emoji, sticker, message, invite, thread, event info handlers to send via interaction followup when applicable
  • Ensure cases views, modifications, and error paths use followup for interactions and distinguish between ephemeral and non-ephemeral cases
  • Improve error messaging for guild lookup and not-found cases in info command, using ephemeral slash responses
src/tux/modules/info/info.py
src/tux/modules/moderation/cases.py
Upgrade Python toolchain references from 3.13.8 to 3.13.11 and add mise support.
  • Bump PYTHON_VERSION in all GitHub workflows and custom setup-python composite action
  • Update Containerfile base image tag and related docs to 3.13.11-slim
  • Adjust pre-commit default python version and add mise.toml with python=3.13.11
  • Update docs references to match new Python version
.github/workflows/tests.yml
.github/workflows/ci.yml
.github/workflows/docker-push.yml
.github/workflows/docker.yml
.github/workflows/docs.yml
.github/workflows/security.yml
.github/actions/setup-python/action.yml
.pre-commit-config.yaml
Containerfile
docs/content/developer/best-practices/ci-cd.md
docs/content/reference/docker-production.md
docs/content/reference/renovate.md
docs/content/selfhost/config/database.md
docs/content/selfhost/manage/docker.md
mise.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR is a comprehensive multi-faceted update that includes: (1) bumping Python runtime from 3.13.8 to 3.13.11 across CI/CD workflows and Docker configurations, (2) adding database sequence repair tooling and performance indexes, (3) implementing slash command interaction handling with deferred responses and context-aware messaging across moderation and utility modules, (4) introducing intent-based bot configuration and HTTP client optimization, (5) implementing TTL caching for guild configurations, and (6) improving permission system efficiency through batching.

Changes

Cohort / File(s) Summary
Python Version Upgrades (3.13.8 → 3.13.11)
.github/actions/setup-python/action.yml, .github/workflows/ci.yml, .github/workflows/docker-push.yml, .github/workflows/docker.yml, .github/workflows/docs.yml, .github/workflows/security.yml, .github/workflows/tests.yml, .python-version, Containerfile, docs/content/developer/best-practices/ci-cd.md, docs/content/reference/docker-production.md, docs/content/reference/renovate.md, docs/content/selfhost/config/database.md, docs/content/selfhost/manage/docker.md
Updated Python version references from 3.13.8 to 3.13.11 in all GitHub Actions, Docker image bases, environment configurations, and documentation.
Configuration and Tool Management
mise.toml, src/tux/shared/config/models.py, src/tux/shared/config/settings.py
Added mise.toml with Python 3.13.11; introduced BotIntents Pydantic model with to_discord_intents() method and BOT_INTENTS field in Config.
Database Performance
src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py, src/tux/database/models/models.py, src/tux/database/controllers/case.py
Added Alembic migration creating two composite indexes on cases table (guild+number, and filtered tempbans); updated Case model index definitions; added ID filtering in create_case to prevent manual ID assignment.
Guild Configuration Caching
src/tux/shared/cache.py, src/tux/shared/__init__.py, src/tux/database/controllers/guild_config.py, src/tux/services/moderation/communication_service.py, src/tux/ui/views/config/dashboard.py
Implemented TTLCache and GuildConfigCacheManager singleton for caching guild log channel IDs; updated guild_config controller to invalidate cache on updates; integrated caching in communication_service for batch log channel retrieval.
Core Bot Configuration and HTTP
src/tux/core/app.py, src/tux/core/bot.py, src/tux/core/http_config.py
Refactored bot intents to use CONFIG.BOT_INTENTS.to_discord_intents(); added status=discord.Status.online; introduced configure_discord_http_client for aiohttp tuning; added first_ready flag for one-time initialization.
Utilities and Converters
src/tux/core/converters.py, src/tux/core/base_cog.py, src/tux/core/decorators.py
Added get_user_safe utility for cache-first user resolution; added send_after_defer method to BaseCog for unified message sending; enhanced decorator logging with command name context and error handling.
Permission System Enhancements
src/tux/core/permission_system.py, src/tux/help/data.py, src/tux/help/help.py, src/tux/help/navigation.py
Introduced batch_get_command_permissions for efficient multi-command permission lookups; added batch_can_run_commands to help system with caching; replaced per-command permission checks with batched operations in group help and navigation.
Database Repair Tooling
scripts/db/fix_sequences.py, scripts/db/__init__.py
Added new CLI command fix-sequences for PostgreSQL sequence synchronization repair with dry-run support; includes discovery, status checks, planning, and reset workflows with Discord snowflake ID skipping.
Moderation Module Interaction Handling
src/tux/modules/moderation/ban.py, src/tux/modules/moderation/kick.py, src/tux/modules/moderation/tempban.py, src/tux/modules/moderation/warn.py, src/tux/modules/moderation/clearafk.py, src/tux/modules/moderation/pollban.py, src/tux/modules/moderation/pollunban.py, src/tux/modules/moderation/snippetban.py, src/tux/modules/moderation/snippetunban.py, src/tux/modules/moderation/timeout.py, src/tux/modules/moderation/untimeout.py, src/tux/modules/moderation/unban.py
Added early interaction deferrals (ctx.defer(ephemeral=True)) and context-aware response routing via interaction.followup.send for slash commands with fallback to ctx.send/ctx.reply for text commands.
Advanced Moderation Modules
src/tux/modules/moderation/cases.py, src/tux/modules/moderation/jail.py, src/tux/modules/moderation/unjail.py, src/tux/modules/moderation/slowmode.py
Added _respond helper methods for unified message sending; refactored interaction handling and permission validation; improved channel resolution and error message routing through interaction-aware paths; updated method signatures for flags support.
Moderation Services
src/tux/services/moderation/execution_service.py, src/tux/services/moderation/moderation_coordinator.py
Enhanced ExecutionService with circuit breaker cleanup (LRU eviction, max entries) and refined error semantics (non-counting for Forbidden/NotFound); refactored moderation_coordinator to parallelize case creation and embed sending with new helper methods and expiration handling.
Utility Modules - Interaction Support
src/tux/modules/utility/afk.py, src/tux/modules/utility/remindme.py, src/tux/modules/utility/self_timeout.py, src/tux/modules/utility/wiki.py
Added interaction deferrals and context-aware response routing; refactored remindme to make reminder keyword-only; introduced helper methods in self_timeout for validation and messaging; centralized response handling via _send_response and _send_afk_response.
Info and Fun Modules
src/tux/modules/info/avatar.py, src/tux/modules/info/info.py, src/tux/modules/levels/level.py, src/tux/modules/levels/levels.py, src/tux/modules/fun/xkcd.py
Added interaction deferrals; refactored info with converter-based resolution and banner fetching; improved avatar and level commands with context-aware follow-up messaging; consolidated xkcd response building with kwargs pattern.
Event and Activity Handlers
src/tux/services/handlers/event.py, src/tux/services/handlers/activity.py, src/tux/modules/modules/features/starboard.py, src/tux/modules/modules/features/status_roles.py
Added first_ready tracking to prevent duplicate expensive operations on reconnects; introduced 2-second delay before activity rotation; added batched/chunked member processing with rate-limit mitigation in status roles; refactored starboard helpers for error embed creation.
Error Handling
src/tux/services/handlers/error/cog.py, src/tux/shared/exceptions.py
Enhanced error logging with source context extraction (command name, guild ID); updated TuxPermissionDeniedError to inherit from both commands.CheckFailure and app_commands.CheckFailure for unified error routing.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • allthingslinux/tux#1080 — Changes to src/tux/database/controllers/guild_config.py add get_log_channel_ids() method and cache invalidation which directly support guild log channel retrieval APIs referenced in the issue.

Possibly related PRs

  • refactor(docker) #1130 — Both this PR and the retrieved PR modify Docker-related files (Containerfile, Docker workflows, and docker documentation), with overlapping changes to Python image versions and Docker base image references.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Misc refactorings and optimizations' is vague and generic, using non-descriptive language that does not clearly convey the actual scope and nature of the extensive changes. Consider a more specific title that highlights the main focus, such as 'Refactor interaction handling and optimize permissions with configurable intents' or 'Add sequence repair tooling and improve command interaction consistency'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is a template with placeholders and checkboxes, but the 'Summary by Sourcery' section provides meaningful details about the changes including new features, enhancements, build updates, and documentation changes.
Docstring Coverage ✅ Passed Docstring coverage is 98.94% which is sufficient. The required threshold is 80.00%.

✏️ 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
  • Commit unit tests in branch refactor/optimizations

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.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 22, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

…starboard configuration

- Added early deferral for interactions to acknowledge user input before processing.
- Refactored error handling to utilize a dedicated embed for error messages, improving user feedback and consistency in responses.
- Streamlined the response logic by consolidating embed sending into a single method.
…bans

- Introduced a composite index for efficient case lookups by guild and case number.
- Added a composite index for querying expired temporary bans, optimizing performance for specific case status and type conditions.
- Consolidated response logic in the xkcd commands to improve type safety and reduce code duplication.
- Utilized a kwargs dictionary to conditionally build response parameters for sending embeds, views, and ephemeral messages.
- Enhanced response logic to conditionally build payloads for sending messages and embeds, ensuring type safety.
- Implemented checks for interaction response status to streamline message sending, improving user experience with ephemeral messages.
- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience by ensuring timely feedback during ban operations.
- Added conditional checks to defer interaction responses only when an interaction is present, improving user experience by ensuring timely feedback during case operations.
- Updated message sending logic to remove ephemeral flag when not in an interaction context, streamlining response handling.
…ClearAFK module

- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience during AFK status clearing.
- Streamlined message sending by removing the ephemeral flag when not in an interaction context, ensuring clearer feedback for users.
… Jail module

- Added conditional checks to defer interaction responses only when an interaction is present, improving user experience during jail operations.
- Updated message sending logic to use follow-up messages for interactions, ensuring clearer feedback for users when no jail role or channel is found, or if the user is already jailed.
…n Slowmode module

- Enhanced interaction deferral to acknowledge user input early when an interaction is present, improving user experience during slowmode operations.
- Refactored message sending logic to utilize a dedicated method for consistent response handling, ensuring clarity in feedback for users across different contexts.
- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience during kick operations.
…odules

- Added conditional checks to defer interaction responses only when an interaction is present in PollBan, PollUnban, SnippetBan, and SnippetUnban modules, enhancing user experience during moderation operations.
- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience during temporary ban operations.
- Added a conditional check to defer interaction responses only when an interaction is present, improving user experience during unban operations.
… Timeout and Untimeout modules

- Added conditional checks to defer interaction responses only when an interaction is present, improving user experience during timeout and untimeout operations.
- Updated message sending logic to utilize follow-up messages for interactions, ensuring consistent feedback for users across different contexts.
- Added a conditional check to defer interaction responses only when an interaction is present, improving user experience during unjail operations.
- Updated the reminder method to accept the reminder parameter as a keyword-only argument, improving clarity and usability when setting reminders.
…fTimeout module

- Removed the ephemeral flag from message replies when not in an interaction context, ensuring clearer feedback for users.
- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience during self-timeout operations.
- Added a conditional check to defer interaction responses only when an interaction is present, enhancing user experience during warning operations.
- Updated the _log_error method to include the source context (command or interaction) and guild ID in the error logs, improving diagnosability of issues.
- Refactored logging statements to provide clearer feedback on encountered errors, including additional context for better troubleshooting.
- Updated the EventHandler to mark the first ready state even if the bot setup is incomplete or fails, preventing unnecessary expensive checks on retries.
- This change improves the reliability of the bot's readiness state handling.
…ommunicationService and ExecutionService

- Integrated a shared cache manager for guild configurations in CommunicationService, improving efficiency in fetching log channel IDs.
- Updated embed sending logic to differentiate between slash and prefix commands, ensuring consistent user feedback.
- Enhanced ExecutionService with circuit breaker improvements, including LRU eviction for circuit entries and periodic cleanup to manage memory usage effectively.
- Refactored moderation coordinator to execute Discord actions and case creation in parallel, optimizing performance during moderation actions.
- Added a new TTLCache class for thread-safe caching with automatic expiration of entries.
- Implemented a GuildConfigCacheManager for managing guild configuration data, ensuring consistency and efficient access.
- Updated the shared module to include TTLCache in the public API.
- Added a comment to indicate that cache invalidation is automatically managed by GuildConfigController.update_config, improving code clarity and maintainability.
- Replaced suppress with try-except for better error handling during channel conversion, ensuring that a None return value is properly managed in case of a CommandError.
- Changed the default Python version from 3.13.11 to 3.13 for consistency with the latest stable release.
Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (4)
src/tux/modules/moderation/unjail.py (2)

173-186: Bug: Using ctx.reply after ctx.defer() will fail for interactions.

After deferring an interaction with ctx.defer(), you must use ctx.interaction.followup.send() for subsequent messages, not ctx.reply(). The current code defers when ctx.interaction exists (line 175) but then uses ctx.reply() for error responses (lines 180, 185), which will fail for slash command invocations.

This should follow the same interaction-aware pattern used in jail.py.

🐛 Proposed fix
         # Get jail role
         jail_role = await self.get_jail_role(ctx.guild)
         if not jail_role:
-            await ctx.reply("No jail role found.", mention_author=False)
+            if ctx.interaction:
+                await ctx.interaction.followup.send("No jail role found.", ephemeral=True)
+            else:
+                await ctx.reply("No jail role found.", mention_author=False)
             return
 
         # Check if user is jailed
         if not await self.is_jailed(ctx.guild.id, member.id):
-            await ctx.reply("User is not jailed.", mention_author=False)
+            if ctx.interaction:
+                await ctx.interaction.followup.send("User is not jailed.", ephemeral=True)
+            else:
+                await ctx.reply("User is not jailed.", mention_author=False)
             return

192-204: Same issue: ctx.reply used after defer inside perform_unjail.

Line 203 also uses ctx.reply() after the interaction has been deferred, which will fail for slash commands. Apply the same fix pattern here.

🐛 Proposed fix
             case = await self.get_latest_jail_case(guild_id, member.id)
             if not case:
-                await ctx.reply("No jail case found.", mention_author=False)
+                if ctx.interaction:
+                    await ctx.interaction.followup.send("No jail case found.", ephemeral=True)
+                else:
+                    await ctx.reply("No jail case found.", mention_author=False)
                 return
src/tux/modules/moderation/unban.py (1)

133-148: Inconsistent response handling after defer.

After deferring the interaction at line 135, the code uses ctx.reply() at lines 144-147 and 155. For slash commands, after a defer you must use ctx.interaction.followup.send() instead—ctx.reply() will fail or behave unexpectedly.

Other commands in this PR (e.g., timeout, untimeout, slowmode) correctly use the followup pattern after deferring.

🐛 Proposed fix
         # First, try standard user conversion
         try:
             user = await commands.UserConverter().convert(ctx, username_or_id)
         except commands.UserNotFound:
             # If that fails, try more flexible ban list matching
             user = await self.resolve_user_from_ban_list(ctx, username_or_id)
             if not user:
-                await ctx.reply(
-                    f"Could not find '{username_or_id}' in the ban list. Try using the exact username or ID.",
-                    mention_author=False,
-                )
+                msg = f"Could not find '{username_or_id}' in the ban list. Try using the exact username or ID."
+                if ctx.interaction:
+                    await ctx.interaction.followup.send(msg, ephemeral=True)
+                else:
+                    await ctx.reply(msg, mention_author=False)
                 return
 
         # Check if the user is banned
         try:
             await ctx.guild.fetch_ban(user)
 
         except discord.NotFound:
-            await ctx.reply(f"{user} is not banned.", mention_author=False)
+            msg = f"{user} is not banned."
+            if ctx.interaction:
+                await ctx.interaction.followup.send(msg, ephemeral=True)
+            else:
+                await ctx.reply(msg, mention_author=False)
             return
src/tux/database/controllers/guild_config.py (1)

61-91: Ensure cache invalidation covers all log-channel update paths

update_config invalidates the cache, but the per-field helpers (update_config_field / update_channel_field) call update_by_id directly. That means log-channel changes through those methods won’t invalidate the cache, leaving stale audit/mod IDs until TTL expiry.

Consider routing field updates through update_config (or invalidating inside update_config_field) to keep log routing consistent.

Possible adjustment (apply in update_config_field)
return await self.update_config(guild_id, **{field_name: field_value})
🤖 Fix all issues with AI agents
In `@src/tux/modules/moderation/clearafk.py`:
- Line 43: The return type annotation ") -> discord.Message | None:" is
incorrect because the function never returns None; remove the "| None" so it
reads ") -> discord.Message" and update the function's docstring to state it
returns a discord.Message and will raise on send failure (or explicit
exception), not return None; apply the same fix to the other similar signatures
at the other occurrences (lines 56-57) so all type hints and docstrings
consistently reflect that the functions return discord.Message and raise on
failure.

In `@src/tux/modules/moderation/jail.py`:
- Around line 204-241: The current jail command handler uses inconsistent
response methods (ctx.send) and repeats the same interaction-vs-noninteraction
branching three times; update the non-interaction responses to use ctx.reply to
match unjail behavior and extract the repeated pattern into a small helper
(e.g., respond(ctx, message, *, ephemeral=True)) that checks ctx.interaction and
calls ctx.interaction.followup.send(...) or ctx.reply(...); apply this helper in
the code paths around get_jail_role, get_jail_channel, and is_jailed (and in the
main jail command function) so all messages use the helper and non-interaction
paths use ctx.reply consistently.

In `@src/tux/services/handlers/error/cog.py`:
- Around line 200-211: The log-level lookup using getattr(logger,
config.log_level.lower()) can raise AttributeError if config.log_level is
invalid; change the lookup in the error handler so it falls back to a safe
default (e.g., logger.error) by providing a default value when calling getattr
or validating config.log_level first; update the assignment to log_func in
cog.py (the variable named log_func that uses logger and config.log_level) to
use a fallback logger method to ensure the error path cannot itself raise.
🧹 Nitpick comments (11)
src/tux/modules/utility/self_timeout.py (4)

155-167: Consider using member consistently for DM and logging.

The DM is sent to ctx.author (line 158) but logging references member (lines 162, 166). While they represent the same user, using member throughout would be more consistent.

Suggested change
         try:
-            await ctx.author.send(
+            await member.send(
                 f'You have timed yourself out in guild {ctx.guild.name} for {duration_readable} with the reason "{reason}".',
             )

223-227: Add cooldown decorator per coding guidelines.

The command lacks rate limiting, which is required per the coding guidelines for Discord commands. Self-timeout is a significant action that should have abuse prevention.

Suggested change
 `@commands.hybrid_command`(
     name="self_timeout",
     aliases=["sto", "stimeout", "selftimeout"],
 )
 `@commands.guild_only`()
+@commands.cooldown(1, 60, commands.BucketType.user)
 async def self_timeout(

Based on learnings, Discord commands should implement cooldowns and rate limiting.


307-310: Provide feedback when user cancels the timeout.

When the user doesn't confirm, the command returns silently. Adding a brief message improves UX clarity.

Suggested change
     confirmed = await self._send_confirmation_dialog(ctx, message_content)

     if not confirmed:
+        logger.debug(f"Self-timeout cancelled by {member.display_name} ({member.id})")
+        await self._send_error_message(ctx, "Self-timeout cancelled.")
         return

298-305: Consider using duration_readable for consistency.

The warning message uses the raw duration string while other messages use duration_readable. Using the human-readable format throughout provides a more consistent user experience.

Suggested change
         message_content = (
             f'### WARNING\n### You are about to be timed out in the guild "{ctx.guild.name}" '
-            f'for {duration} with the reason "{reason}".\n'
+            f'for {duration_readable} with the reason "{reason}".\n'
             "as soon as you confirm this, **you cannot cancel it or remove it early**. "
scripts/db/fix_sequences.py (1)

125-136: Consider using proper identifier quoting for defense-in-depth.

While sequence_name, table_name, and column_name originate from trusted system catalogs, PostgreSQL identifiers can contain special characters, spaces, or reserved words. Using quote_ident() in the SQL or SQLAlchemy's escaping mechanisms would make this more robust.

♻️ Suggested improvement using PostgreSQL's quote_ident()
     # Get current sequence value
     current_seq_result = await session.execute(
-        text(f"SELECT last_value, is_called FROM {sequence_name}"),
+        text(f"SELECT last_value, is_called FROM {sequence_name}"),  # sequence_name is already schema-qualified
     )
     current_seq_row = current_seq_result.fetchone()
     current_seq_value = current_seq_row[0] if current_seq_row else 0
     is_called = current_seq_row[1] if current_seq_row else False

     # Get max ID from table (table and column are safe identifiers)
+    # For additional safety with edge-case identifiers, consider:
+    # text(f"SELECT COALESCE(MAX(quote_ident('{column_name}')), 0) FROM quote_ident('{table_name}')")
     max_id_result = await session.execute(
         text(f"SELECT COALESCE(MAX({column_name}), 0) FROM {table_name}"),
     )

Alternatively, you could build the query using PostgreSQL's format() function with %I placeholders for identifiers.

src/tux/modules/moderation/jail.py (1)

183-189: Signature change looks good, but noqa PLR0912 indicates complexity.

The addition of flags: JailFlags aligns with other moderation commands in the codebase. However, the noqa PLR0912 suppression indicates the method has too many branches. Consider extracting validation logic into helper methods to reduce complexity.

src/tux/modules/moderation/unjail.py (1)

45-48: Docstring uses Optional[discord.Role] instead of discord.Role | None.

Per coding guidelines, prefer Type | None syntax. The identical method in jail.py (line 56) correctly uses discord.Role | None.

📝 Suggested fix
         Returns
         -------
-        Optional[discord.Role]
+        discord.Role | None
             The jail role, or None if not found.
src/tux/database/models/models.py (1)

698-721: Potentially redundant index idx_case_guild_number.

Line 721 already declares UniqueConstraint("guild_id", "case_number", name="uq_case_guild_case_number"), which implicitly creates a unique index on those columns in PostgreSQL. The explicit idx_case_guild_number non-unique index on the same columns is therefore redundant and adds maintenance overhead without query benefit.

Consider removing idx_case_guild_number since the unique constraint already provides the same lookup performance.

♻️ Proposed fix
         Index("idx_case_processed", "case_processed"),
-        # Composite index for case lookup by guild and case number (common query pattern)
-        Index("idx_case_guild_number", "guild_id", "case_number"),
         # Composite index for expired tempban queries (guild_id, case_type, case_status, case_expires_at)
         Index(
src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py (1)

11-14: Unused import sqlmodel.

The sqlmodel import on line 13 is not used in this migration file.

♻️ Proposed fix
 from alembic import op
 import sqlalchemy as sa
-import sqlmodel
src/tux/modules/moderation/slowmode.py (1)

308-312: Fragile string manipulation for message formatting.

Line 311 splits on 'is' to reuse part of _format_slowmode_message(). This works but creates a brittle dependency on the exact message format. Consider extracting a separate method that returns only the duration description, or restructuring to avoid parsing the formatted output.

♻️ Example improvement
+    `@staticmethod`
+    def _format_duration(delay: int) -> str:
+        """Format delay into a human-readable duration string."""
+        if delay == 0:
+            return "disabled"
+        if delay == 1:
+            return "1 second"
+        if delay < 60:
+            return f"{delay} seconds"
+        if delay == 60:
+            return "1 minute"
+        minutes, seconds = divmod(delay, 60)
+        if seconds == 0:
+            return f"{minutes} minutes"
+        minute_suffix = "s" if minutes > 1 else ""
+        second_suffix = "s" if seconds > 1 else ""
+        return f"{minutes} minute{minute_suffix} and {seconds} second{second_suffix}"
+
     `@staticmethod`
     def _format_slowmode_message(delay: int, channel_mention: str) -> str:
-        # ... existing implementation
+        duration = Slowmode._format_duration(delay)
+        if delay == 0:
+            return f"Slowmode is disabled in {channel_mention}."
+        return f"The slowmode in {channel_mention} is {duration}."

Then in _set_slowmode:

             if delay_seconds == 0:
                 message = f"Slowmode has been disabled in {channel.mention}."
             else:
-                prefix = "Slowmode set to"
-                message = f"{prefix} {self._format_slowmode_message(delay_seconds, channel.mention).split('is')[1].strip()}"
+                message = f"Slowmode set to {self._format_duration(delay_seconds)} in {channel.mention}."
src/tux/modules/info/info.py (1)

337-377: Interaction guard is correctly implemented; consider adding cooldown.

The if ctx.interaction: guard on line 358 correctly addresses the previous review concern about prefix command support.

Per coding guidelines, consider adding a cooldown to prevent abuse:

💡 Optional cooldown decorator
 `@commands.hybrid_group`(
     name="info",
     aliases=["i"],
 )
 `@commands.guild_only`()
+@commands.cooldown(1, 5, commands.BucketType.user)
 async def info(
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cc34a6 and f2546a8.

📒 Files selected for processing (35)
  • scripts/db/fix_sequences.py
  • src/tux/core/decorators.py
  • src/tux/core/http_config.py
  • src/tux/database/controllers/guild_config.py
  • src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py
  • src/tux/database/models/models.py
  • src/tux/modules/features/starboard.py
  • src/tux/modules/fun/xkcd.py
  • src/tux/modules/info/info.py
  • src/tux/modules/moderation/ban.py
  • src/tux/modules/moderation/cases.py
  • src/tux/modules/moderation/clearafk.py
  • src/tux/modules/moderation/jail.py
  • src/tux/modules/moderation/kick.py
  • src/tux/modules/moderation/pollban.py
  • src/tux/modules/moderation/pollunban.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/modules/moderation/snippetban.py
  • src/tux/modules/moderation/snippetunban.py
  • src/tux/modules/moderation/tempban.py
  • src/tux/modules/moderation/timeout.py
  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/modules/moderation/untimeout.py
  • src/tux/modules/moderation/warn.py
  • src/tux/modules/utility/remindme.py
  • src/tux/modules/utility/self_timeout.py
  • src/tux/services/handlers/error/cog.py
  • src/tux/services/handlers/event.py
  • src/tux/services/moderation/communication_service.py
  • src/tux/services/moderation/execution_service.py
  • src/tux/services/moderation/moderation_coordinator.py
  • src/tux/shared/__init__.py
  • src/tux/shared/cache.py
  • src/tux/ui/views/config/dashboard.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/tux/modules/moderation/ban.py
  • src/tux/modules/moderation/pollban.py
  • src/tux/modules/moderation/warn.py
  • src/tux/modules/fun/xkcd.py
  • src/tux/modules/utility/remindme.py
  • src/tux/modules/moderation/snippetunban.py
  • src/tux/modules/moderation/snippetban.py
🧰 Additional context used
📓 Path-based instructions (9)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use strict type hints with Type | None syntax instead of Optional[Type] in Python
Use NumPy docstring format for documenting Python functions and classes
Prefer absolute imports, with relative imports allowed only within the same module
Organize imports in order: stdlib → third-party → local, with imports at the top of the file unless absolutely necessary
Use 88 character line length for Python code
Use snake_case for functions and variables, PascalCase for classes, UPPER_CASE for constants in Python
Ensure all type hints are complete for function signatures
Add docstrings for all public APIs
Keep Python files to a maximum of 1600 lines
Use async/await for I/O operations instead of blocking calls
Use custom exceptions for business logic errors
Log with context when handling errors
Do not store secrets in code, use environment variables for configuration
Validate all user inputs in Python code
One class or function per file when possible

**/*.py: Follow input validation patterns from security/validation.mdc
Follow error patterns from error-handling/patterns.mdc
Follow logging patterns using loguru from error-handling/logging.mdc
Follow Sentry integration patterns from error-handling/sentry.mdc
Follow user-facing error message patterns from error-handling/user-feedback.mdc
Follow Discord.py Components V2 rules from ui/cv2.mdc

Files:

  • src/tux/modules/moderation/pollunban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/modules/moderation/cases.py
  • src/tux/services/handlers/event.py
  • src/tux/modules/moderation/untimeout.py
  • src/tux/modules/features/starboard.py
  • src/tux/ui/views/config/dashboard.py
  • src/tux/services/moderation/communication_service.py
  • src/tux/modules/moderation/tempban.py
  • src/tux/services/moderation/execution_service.py
  • src/tux/database/models/models.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/shared/__init__.py
  • src/tux/core/decorators.py
  • src/tux/services/handlers/error/cog.py
  • src/tux/database/controllers/guild_config.py
  • scripts/db/fix_sequences.py
  • src/tux/core/http_config.py
  • src/tux/modules/utility/self_timeout.py
  • src/tux/modules/moderation/timeout.py
  • src/tux/services/moderation/moderation_coordinator.py
  • src/tux/shared/cache.py
  • src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py
  • src/tux/modules/moderation/clearafk.py
  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/jail.py
  • src/tux/modules/moderation/kick.py
  • src/tux/modules/info/info.py
src/tux/modules/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/modules/**/*.py: Use hybrid commands (slash and traditional) for Discord bot commands
Implement role-based permissions for Discord commands
Implement cooldowns and rate limiting for Discord commands

Files:

  • src/tux/modules/moderation/pollunban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/modules/moderation/cases.py
  • src/tux/modules/moderation/untimeout.py
  • src/tux/modules/features/starboard.py
  • src/tux/modules/moderation/tempban.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/modules/utility/self_timeout.py
  • src/tux/modules/moderation/timeout.py
  • src/tux/modules/moderation/clearafk.py
  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/jail.py
  • src/tux/modules/moderation/kick.py
  • src/tux/modules/info/info.py
src/tux/services/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/services/**/*.py: Use dependency injection in service classes
Keep service classes stateless where possible and use async/await for I/O
Use caching for frequently accessed data

Files:

  • src/tux/services/handlers/event.py
  • src/tux/services/moderation/communication_service.py
  • src/tux/services/moderation/execution_service.py
  • src/tux/services/handlers/error/cog.py
  • src/tux/services/moderation/moderation_coordinator.py
**/*event*.py

📄 CodeRabbit inference engine (.cursor/rules/rules.mdc)

Follow Discord event handler patterns from modules/events.mdc

Files:

  • src/tux/services/handlers/event.py
src/tux/ui/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use rich embeds for Discord message formatting

Files:

  • src/tux/ui/views/config/dashboard.py
**/*service*.py

📄 CodeRabbit inference engine (.cursor/rules/rules.mdc)

Follow DatabaseService patterns from database/services.mdc

Files:

  • src/tux/services/moderation/communication_service.py
  • src/tux/services/moderation/execution_service.py
src/tux/database/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/database/**/*.py: Use SQLModel for type-safe ORM operations
Use Pydantic for data validation in database models
Use transactions for multi-step database operations
Implement model-level validation in database models
Optimize database queries for performance

Files:

  • src/tux/database/models/models.py
  • src/tux/database/controllers/guild_config.py
  • src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py
**/*models.py

📄 CodeRabbit inference engine (.cursor/rules/rules.mdc)

Follow SQLModel database model patterns from database/models.mdc

Files:

  • src/tux/database/models/models.py
src/tux/database/migrations/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use Alembic for database migrations

Files:

  • src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py
🧠 Learnings (12)
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*interaction*.py : Follow interaction response patterns from `modules/interactions.mdc`

Applied to files:

  • src/tux/modules/moderation/pollunban.py
  • src/tux/modules/moderation/cases.py
  • src/tux/modules/moderation/untimeout.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/ui/**/*.py : Use rich embeds for Discord message formatting

Applied to files:

  • src/tux/modules/features/starboard.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/modules/**/*.py : Use hybrid commands (slash and traditional) for Discord bot commands

Applied to files:

  • src/tux/modules/features/starboard.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/modules/**/*.py : Implement cooldowns and rate limiting for Discord commands

Applied to files:

  • src/tux/modules/moderation/slowmode.py
  • src/tux/core/http_config.py
  • src/tux/modules/utility/self_timeout.py
  • src/tux/modules/moderation/timeout.py
  • src/tux/modules/info/info.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/services/**/*.py : Use caching for frequently accessed data

Applied to files:

  • src/tux/shared/__init__.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/modules/**/*.py : Implement role-based permissions for Discord commands

Applied to files:

  • src/tux/core/decorators.py
  • src/tux/modules/info/info.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to **/*.py : Log with context when handling errors

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow logging patterns using loguru from `error-handling/logging.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow error patterns from `error-handling/patterns.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow user-facing error message patterns from `error-handling/user-feedback.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to **/*.py : Use custom exceptions for business logic errors

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/database/**/*.py : Optimize database queries for performance

Applied to files:

  • src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py
🧬 Code graph analysis (12)
src/tux/modules/moderation/pollunban.py (1)
src/tux/modules/moderation/__init__.py (1)
  • is_pollbanned (135-154)
src/tux/modules/moderation/cases.py (2)
src/tux/database/controllers/__init__.py (2)
  • case (145-149)
  • guild (110-114)
src/tux/database/controllers/case.py (1)
  • get_case_by_number (343-354)
src/tux/modules/features/starboard.py (1)
src/tux/ui/embeds.py (1)
  • EmbedCreator (38-235)
src/tux/shared/__init__.py (1)
src/tux/shared/cache.py (1)
  • TTLCache (19-167)
src/tux/services/handlers/error/cog.py (1)
src/tux/services/handlers/error/config.py (1)
  • ErrorHandlerConfig (38-60)
src/tux/database/controllers/guild_config.py (2)
src/tux/database/models/models.py (1)
  • GuildConfig (174-323)
src/tux/shared/cache.py (1)
  • GuildConfigCacheManager (170-246)
src/tux/core/http_config.py (1)
src/tux/core/bot.py (1)
  • Tux (46-495)
src/tux/modules/utility/self_timeout.py (3)
src/tux/ui/views/confirmation.py (1)
  • ConfirmationDanger (92-107)
src/tux/modules/utility/__init__.py (2)
  • add_afk (40-64)
  • del_afk (67-80)
src/tux/shared/functions.py (2)
  • convert_to_seconds (125-190)
  • seconds_to_human_readable (193-222)
src/tux/services/moderation/moderation_coordinator.py (4)
src/tux/database/controllers/case.py (2)
  • update_mod_log_message_id (211-231)
  • create_case (80-198)
src/tux/services/moderation/case_service.py (2)
  • update_mod_log_message_id (131-154)
  • create_case (36-77)
src/tux/services/moderation/communication_service.py (1)
  • send_dm (41-89)
src/tux/database/models/models.py (1)
  • Case (569-726)
src/tux/modules/moderation/clearafk.py (1)
src/tux/database/controllers/afk.py (1)
  • is_afk (142-151)
src/tux/modules/moderation/jail.py (2)
src/tux/modules/moderation/unjail.py (1)
  • get_jail_role (36-51)
src/tux/modules/moderation/__init__.py (1)
  • is_jailed (111-133)
src/tux/modules/info/info.py (1)
src/tux/core/converters.py (1)
  • get_user_safe (179-209)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Seer Code Review
  • GitHub Check: Run All Tests (3.13.11)
🔇 Additional comments (90)
src/tux/core/decorators.py (3)

104-113: LGTM! Good refactor to consolidate command name extraction.

Extracting command_name once at the start and using it consistently throughout the permission flow improves maintainability. The fallback to func.__name__ ensures logging works even in edge cases before the ValueError is raised at line 115.


139-141: LGTM! Better audit trail for permission bypasses.

Including command_name in the bypass log message improves traceability when debugging permission issues.


146-148: LGTM! Consistent logging pattern.

Matches the logging pattern used for bot owner/sysadmin bypass, maintaining consistency across permission bypass scenarios.

src/tux/modules/utility/self_timeout.py (4)

34-51: LGTM!

The helper correctly handles both interaction and prefix command contexts, with ephemeral appropriately applied only to the interaction branch.


53-93: LGTM!

Clean validation logic with appropriate bounds checking and debug logging. The walrus operator usage at the call site (line 276) is idiomatic.


95-133: LGTM!

The confirmation dialog helper properly handles both command contexts and includes robust exception handling for message deletion. Past review concerns have been addressed.


177-221: LGTM!

The timeout application and AFK status setting logic is well-structured. The assert member.guild is not None on line 209 is acceptable here since guild validation occurs in the calling command context.

scripts/db/fix_sequences.py (12)

1-28: LGTM!

Module docstring clearly explains purpose, and imports are correctly organized following the stdlib → third-party → local convention with complete type annotations.


30-51: LGTM!

The SQL query correctly discovers serial-backed sequences using pg_get_serial_sequence(), and the Discord snowflake threshold is appropriate for filtering externally-provided IDs.


53-64: LGTM!

Clean dataclass definition with clear field names and types for representing a sequence fix plan.


66-98: LGTM!

The function correctly preserves schema-qualified sequence names, addressing the previous review concern about search_path dependency.


153-172: LGTM!

The setval() usage follows correct PostgreSQL syntax. The commit per reset is appropriate for a maintenance CLI to ensure each fix is persisted independently.


175-205: LGTM!

Pure function with clear filtering logic. The max(status["max_id"], 1) correctly handles empty tables by ensuring the sequence starts at 1.


208-271: LGTM!

The lambda pattern with default arguments correctly captures loop variables to avoid late binding issues. The redundant condition checks (lines 245, 254) before plan_sequence_fix are intentional for providing user feedback on skipped sequences.


274-313: LGTM!

Clean separation of dry-run vs actual application logic. The lambda pattern correctly captures loop variables.


316-342: LGTM!

The dry-run summary now correctly reports "Would fix N sequence(s)" when sequences need fixing, addressing the previous review concern about misleading messages.


345-383: LGTM!

Clean orchestration with proper resource cleanup in the finally block. The exception handling is appropriate for an admin maintenance CLI tool.


386-412: LGTM!

Standard Typer CLI pattern with clear help text and informative user messaging before execution.


415-416: LGTM!

Standard script entry point pattern.

src/tux/modules/moderation/tempban.py (1)

57-60: LGTM! Early defer pattern is correctly implemented.

The pattern correctly checks for ctx.interaction before deferring, ensuring prefix commands are unaffected. Ephemeral deferral is appropriate for moderation commands to keep channel activity minimal.

The moderate_user method in the base class properly handles the deferred state through the CommunicationService.send_embed() method, which explicitly uses ctx.interaction.followup.send() for slash commands and ctx.send() for prefix commands—exactly the interaction-aware pattern needed.

src/tux/modules/moderation/clearafk.py (1)

61-63: Interaction deferral and follow-ups look consistent.

Also applies to: 66-71, 87-91

src/tux/services/handlers/event.py (3)

34-38: LGTM!

The early-exit path correctly marks first_ready to prevent expensive startup checks on reconnects, even when setup hasn't completed. The guard and comment are clear.


78-84: LGTM!

The first-ready marking after successful guild registration is well-placed, with appropriate debug logging for observability. The comment accurately describes the intent.


86-92: LGTM!

Properly ensures first_ready is set and waiters are unblocked even on failure, preventing hangs and expensive retry checks. The exception is correctly logged and re-raised.

src/tux/core/http_config.py (4)

26-35: LGTM!

The TYPE_CHECKING guard correctly addresses the circular import with bot.py, and import ordering follows the coding guidelines (stdlib → third-party → local).


37-77: LGTM!

The helper function is well-designed with keyword-only arguments for optional parameters, proper exception handling for read-only attributes, and complete NumPy-style documentation.


137-181: LGTM!

The attribute configuration attempts are appropriately defensive. The logging levels correctly distinguish between successful configurations (info) and expected failures for constructor-only attributes (debug). The seconds_suffix parameter is correctly applied to time-based values for clearer log output.


103-135: The timing concern is already addressed in the code.

The session may not exist at setup_hook() time—this is a valid concern and is explicitly acknowledged in the warning message ("setup_hook() may be running before login()..."). However, the function already handles this gracefully with defensive checks and early returns. The docstring documents this as a best-effort approach, and the implementation cannot reliably be moved to run after login() since setup_hook() is called as part of the login process itself.

The current implementation is appropriate: it attempts configuration when possible but fails safely with informative logging when the session isn't available yet.

src/tux/services/handlers/error/cog.py (2)

128-129: LGTM — passing source context improves diagnostics.


252-255: LGTM — catch-all logging prevents silent failures.

src/tux/modules/moderation/timeout.py (1)

66-116: LGTM!

The interaction handling is well-implemented with consistent patterns:

  • Early defer acknowledges the interaction before async work
  • All error/status messages correctly route through followup.send for interactions and ctx.send for prefix commands
  • The noqa: PLR0912 suppression is acceptable given the necessary validation branches
src/tux/modules/moderation/untimeout.py (1)

64-77: LGTM!

The interaction handling follows the same consistent pattern as the timeout command—early defer and proper followup routing. Implementation is clean and correct.

src/tux/database/migrations/versions/2026_01_23_0138-0b3288704dea_add_case_perf_indexes.py (1)

23-47: LGTM!

The migration correctly creates and drops the indexes with proper partial index conditions. The upgrade/downgrade operations are reversible and align with the model definitions.

Note: If the idx_case_guild_number index is deemed redundant (per the models.py review), this migration would need to be updated accordingly.

src/tux/modules/moderation/slowmode.py (2)

119-159: LGTM! Good caching strategy for channel resolution.

The _resolve_channel method efficiently prioritizes cached lookups (via get_channel/get_thread) before falling back to the converter, avoiding unnecessary API calls. The use of suppress for error handling is clean.


180-202: LGTM! Centralized response handling.

The _send_response helper provides a clean abstraction for handling both slash and prefix command responses, reducing code duplication across the module.

src/tux/database/models/models.py (1)

701-708: No action needed. The TEMPBAN enum value is correctly defined as "TEMPBAN" in src/tux/database/models/enums.py and matches the string literal used in the postgresql_where clause. The partial index condition will correctly filter on tempban cases.

src/tux/modules/info/info.py (5)

117-167: Well-structured response helper with comprehensive path coverage.

The _send_response method cleanly handles all interaction vs. prefix command scenarios. The logic correctly:

  • Checks if response is already done before choosing followup vs. direct response
  • Handles empty payload edge case
  • Falls back appropriately for prefix commands

169-206: Banner helpers properly address previous review concerns.

The _get_member_banner method correctly uses get_user_safe to fetch the user safely, avoiding the member.user attribute issue. The _get_user_banner static method uses defensive getattr access. Both implementations are clean.


208-268: Converter resolution logic is well-structured.

The method correctly iterates through converters, handles the @everyone role edge case, and returns a clear boolean result. The inline converter list building addresses previous review suggestions.


270-307: Guild ID resolution is clean with no unreachable code.

The refactored _try_guild_id method uses early returns properly, addressing the previous static analysis concern about unreachable code. The 15-20 digit validation for Discord snowflakes is appropriate.


379-423: Consistent use of _send_response across all display methods.

All _show_*_info methods now route through the centralized _send_response helper, reducing duplication and ensuring consistent behavior for both interaction and prefix contexts.

src/tux/ui/views/config/dashboard.py (1)

2339-2364: Documentation comment accurately describes caching behavior.

The added comment on line 2359 correctly notes that cache invalidation is handled by GuildConfigController.update_config. This improves code clarity without changing behavior.

src/tux/shared/__init__.py (1)

1-11: Clean public API exposure of TTLCache.

The re-export pattern is appropriate for shared utilities. The TTLCache class (from cache.py) provides a useful thread-safe caching mechanism with TTL support.

src/tux/modules/moderation/kick.py (1)

59-61: Early defer with interaction guard is correctly implemented.

The pattern if ctx.interaction: await ctx.defer(ephemeral=True) correctly acknowledges slash command interactions before async work while preserving prefix command compatibility.

src/tux/modules/moderation/pollunban.py (1)

62-75: Interaction-aware response handling is correctly implemented.

The early defer with guard (lines 62-64) and conditional followup vs. reply (lines 68-74) correctly handle both slash and prefix command contexts. The ephemeral flag ensures moderator privacy for slash commands.

src/tux/modules/moderation/cases.py (10)

150-153: Consistent early defer pattern across case commands.

The early defer with interaction guard is correctly applied to the main cases command group.


180-183: LGTM - view subcommand defer.


208-211: LGTM - search subcommand defer.


241-262: Interaction-aware error handling in modify command.

The case-not-found and no-valid-changes error paths correctly use interaction.followup.send when an interaction exists, with appropriate fallbacks to ctx.send.


299-311: LGTM - _view_all_cases interaction handling.


313-339: LGTM - _view_single_case interaction handling.


369-385: LGTM - _view_cases_with_flags interaction handling.


416-435: LGTM - _update_case failure path handling.


587-643: LGTM - _send_case_embed interaction handling.

Both the error case (lines 617-621) and success case (lines 640-643) correctly use interaction followup when available.


663-673: LGTM - _handle_case_list_response empty case handling.

src/tux/database/controllers/guild_config.py (1)

550-571: Batch log-channel fetch helper looks good

Clean, single-query access pattern that aligns with the cache usage in services.

src/tux/modules/features/starboard.py (6)

45-62: Unified embed send helper is clean

Centralizes interaction vs prefix handling neatly.


64-86: Emoji validation helper is clear

Concise and reusable validation path.


88-110: Threshold validation helper is straightforward

Good guardrail for minimum values.


112-140: Channel permission check helper is tidy

Keeps permission validation in one place.


250-320: Setup flow now consistent with interaction deferral

Early defer + centralized validation/readable response handling is solid.


337-384: Removal flow uses unified response handling

Consistent send path and error embed behavior.

src/tux/services/moderation/communication_service.py (4)

38-40: Shared guild-config cache wiring looks good

Keeps cache access centralized and reusable.


198-229: Interaction-aware send path is consistent

Clear split between followup vs prefix send.


238-272: Cache-backed log channel lookup is efficient

Nice single-query fetch and cache fill on misses.


274-284: Explicit cache invalidation helper is handy

Good encapsulation for targeted invalidation.

src/tux/shared/cache.py (12)

34-48: TTLCache initialization is straightforward

Defaults and state setup are clear.


49-76: Expiration-on-access logic looks good

Simple and predictable cleanup on reads.


78-98: Eviction and TTL assignment are clear

FIFO eviction with TTL is easy to reason about.


100-116: Invalidate supports single-key and full clear

Good dual-path invalidation.


118-145: get_or_fetch utility is concise

Clean helper for cache fill behavior.


146-163: Size calculation with expiry cleanup is fine

Ensures size reflects live entries.


165-167: Clear delegating to invalidate is fine

Small and consistent API.


182-187: Singleton initialization looks good

Encapsulates cache creation neatly.


189-204: Cache keying for guild config is clean

Simple and consistent key format.


206-228: Guild config cache set is clear

Stores audit/mod IDs in a simple payload.


230-241: Per-guild invalidation helper is solid

Clear path for targeted cache resets.


243-246: Clear-all helper is straightforward

Good complement to per-key invalidation.

src/tux/services/moderation/execution_service.py (6)

34-68: LRU-ready circuit state wiring looks good

OrderedDict + config wiring is clean.


114-118: Reduced retry logging is sensible

Keeps logs useful without noise.


180-182: Cleanup hook before circuit check is sensible

Ensures stale entries are pruned early.


196-233: Cleanup/eviction logic is clear

Good balance between time-based and size-based pruning.


243-250: LRU ordering on success is good

Keeps the most relevant entries fresh.


263-270: LRU ordering on failure is good

Consistent MRU updates for failure paths.

src/tux/services/moderation/moderation_coordinator.py (6)

153-282: Parallel case creation and embed sending is well-structured

The orchestration reads clearly and keeps independent work concurrent.


375-387: Post-action DM timeout handling is clear

Increased timeout and explicit logging are sensible.


396-440: Async case creation helper is clean

Good separation for parallel execution.


442-496: Shared base embed builder reduces duplication

Keeps response and mod-log formatting aligned.


498-513: Response embed helper is straightforward

Clear send path and logging.


524-533: Expiry field addition is correct

Nice touch for temporal cases in mod logs.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@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

🤖 Fix all issues with AI agents
In `@src/tux/services/moderation/moderation_coordinator.py`:
- Around line 242-279: The asyncio.gather call that awaits response_task and
mod_log_task can raise and abort the flow; change the await to use
asyncio.gather(..., return_exceptions=True) and then inspect the results so you
log any exceptions from the response or mod-log send (referencing response_task,
mod_log_task, _send_response_embed and _send_mod_log_embed), treat exceptions
separately from successful results, only use mod_log_message and call
_case_service.update_mod_log_message_id when the mod-log send succeeded (i.e.
result is not an Exception and has an id), and log failures instead of letting
them propagate so the rest of the method continues.
♻️ Duplicate comments (1)
src/tux/modules/moderation/unjail.py (1)

36-57: Duplicate _respond helper - see comment in jail.py.

This is the same helper duplicated from jail.py. Move to ModerationCogBase as suggested in the other file's review.

🧹 Nitpick comments (10)
src/tux/modules/moderation/jail.py (1)

45-66: Extract _respond helper to ModerationCogBase to eliminate duplication.

This helper is duplicated verbatim in both jail.py and unjail.py. Since both cogs inherit from ModerationCogBase, move this method to the base class to follow DRY principles.

♻️ Suggested refactor

Move the _respond method to src/tux/modules/moderation/__init__.py (ModerationCogBase):

# In ModerationCogBase class
async def _respond(
    self,
    ctx: commands.Context[Tux],
    message: str,
    *,
    ephemeral: bool = True,
) -> None:
    """Send a response message, handling both slash and prefix commands."""
    if ctx.interaction:
        await ctx.interaction.followup.send(message, ephemeral=ephemeral)
    else:
        await ctx.reply(message, mention_author=False)

Then remove the duplicated method from both jail.py and unjail.py.

src/tux/database/controllers/guild_config.py (2)

86-89: Remove redundant logging statement.

GuildConfigCacheManager.invalidate() already logs a debug message: "Invalidated guild config cache for guild {guild_id}". The additional log statement here creates duplicate entries for the same event.

♻️ Suggested fix
         if result and any(
             field in updates
             for field in (
                 "audit_log_id",
                 "mod_log_id",
                 "join_log_id",
                 "private_log_id",
                 "report_log_id",
                 "dev_log_id",
             )
         ):
             GuildConfigCacheManager().invalidate(guild_id)
-            logger.debug(
-                f"Invalidated guild config cache for guild {guild_id} after config update",
-            )

         return result

93-102: Consider invalidating cache on config deletion.

When a guild config is deleted, any cached entry for that guild becomes stale. While the TTL will eventually expire the entry, explicitly invalidating ensures immediate consistency if the guild rejoins or the config is recreated.

♻️ Suggested fix
     async def delete_config(self, guild_id: int) -> bool:
         """
         Delete guild configuration.

         Returns
         -------
         bool
             True if deleted successfully, False otherwise.
         """
-        return await self.delete_by_id(guild_id)
+        result = await self.delete_by_id(guild_id)
+        if result:
+            GuildConfigCacheManager().invalidate(guild_id)
+        return result
src/tux/modules/moderation/clearafk.py (1)

84-87: Minor typo in comment.

Double space in "if the afk status".

✏️ Proposed fix
         if (
             entry.enforced
-        ):  # untimeout the user if  the afk status is a self-timeout
+        ):  # untimeout the user if the afk status is a self-timeout
             await member.timeout(None, reason="removing self-timeout")
src/tux/modules/moderation/slowmode.py (3)

41-47: Consider adding cooldown/rate limiting.

Per coding guidelines for modules, Discord commands should implement cooldowns. This command lacks a cooldown decorator, which could allow abuse in high-traffic servers.

♻️ Suggested addition
 `@commands.hybrid_command`(
     name="slowmode",
     aliases=["sm"],
     usage="slowmode [channel] [seconds]",
 )
+@commands.cooldown(1, 5, commands.BucketType.user)
 `@commands.guild_only`()
 `@requires_command_permission`()

180-202: Consider adding is_done() check for robustness.

This implementation always uses followup.send(), which works because the interaction is deferred earlier in the command flow. However, the reference pattern in info.py checks ctx.interaction.response.is_done() before deciding between followup.send and response.send_message. This makes the helper more defensive and reusable.

♻️ Suggested improvement for consistency
     `@staticmethod`
     async def _send_response(
         ctx: commands.Context[Tux],
         content: str,
         *,
         ephemeral: bool = True,
     ) -> None:
         ...
         if ctx.interaction:
-            await ctx.interaction.followup.send(content, ephemeral=ephemeral)
+            if ctx.interaction.response.is_done():
+                await ctx.interaction.followup.send(content, ephemeral=ephemeral)
+            else:
+                await ctx.interaction.response.send_message(content, ephemeral=ephemeral)
         else:
             await ctx.send(content)

309-312: Fragile string manipulation coupled to formatter output.

Line 311 splits on 'is' and takes the second part, relying on _format_slowmode_message always including "is" in its output. This implicit coupling is brittle—if the formatter's wording changes, this will break or produce malformed messages.

Consider either extracting the delay formatting to a separate reusable helper or building the "set" message directly:

♻️ Suggested simplification
         if delay_seconds == 0:
             message = f"Slowmode has been disabled in {channel.mention}."
         else:
-            prefix = "Slowmode set to"
-            message = f"{prefix} {self._format_slowmode_message(delay_seconds, channel.mention).split('is')[1].strip()}"
+            message = f"Slowmode has been set to {delay_seconds} seconds in {channel.mention}."
src/tux/modules/moderation/unban.py (2)

52-53: Docstring uses Optional[Type] instead of Type | None.

Per coding guidelines, prefer Type | None syntax. The type hint at line 39 correctly uses discord.User | None, but the docstring at line 52 uses Optional[discord.User].

📝 Fix docstring to match type hint style
-        Optional[discord.User]
+        discord.User | None
             The user if found, None otherwise.

144-151: Use send_after_defer helper to reduce duplication across error responses.

The dual-path pattern (interaction followup vs. prefix reply) is repeated at lines 148–151 and 160–163. BaseCog.send_after_defer() already handles this pattern and should be used instead of manual checks.

♻️ Refactor using send_after_defer
-                msg = (
-                    f"Could not find '{username_or_id}' in the ban list. "
-                    "Try using the exact username or ID."
-                )
-                if ctx.interaction:
-                    await ctx.interaction.followup.send(msg, ephemeral=True)
-                else:
-                    await ctx.reply(msg, mention_author=False)
+                await self.send_after_defer(
+                    ctx,
+                    f"Could not find '{username_or_id}' in the ban list. "
+                    "Try using the exact username or ID.",
+                    ephemeral=True,
+                    mention_author=False,
+                )
src/tux/services/moderation/moderation_coordinator.py (1)

503-526: Use NumPy docstring format for the new helpers.

The new helper docstrings are one‑liners; consider converting them to the standard NumPy format for consistency across this module. As per coding guidelines, ...

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2546a8 and 0c4ea44.

📒 Files selected for processing (8)
  • src/tux/database/controllers/guild_config.py
  • src/tux/modules/moderation/clearafk.py
  • src/tux/modules/moderation/jail.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/services/handlers/error/cog.py
  • src/tux/services/moderation/moderation_coordinator.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use strict type hints with Type | None syntax instead of Optional[Type] in Python
Use NumPy docstring format for documenting Python functions and classes
Prefer absolute imports, with relative imports allowed only within the same module
Organize imports in order: stdlib → third-party → local, with imports at the top of the file unless absolutely necessary
Use 88 character line length for Python code
Use snake_case for functions and variables, PascalCase for classes, UPPER_CASE for constants in Python
Ensure all type hints are complete for function signatures
Add docstrings for all public APIs
Keep Python files to a maximum of 1600 lines
Use async/await for I/O operations instead of blocking calls
Use custom exceptions for business logic errors
Log with context when handling errors
Do not store secrets in code, use environment variables for configuration
Validate all user inputs in Python code
One class or function per file when possible

**/*.py: Follow input validation patterns from security/validation.mdc
Follow error patterns from error-handling/patterns.mdc
Follow logging patterns using loguru from error-handling/logging.mdc
Follow Sentry integration patterns from error-handling/sentry.mdc
Follow user-facing error message patterns from error-handling/user-feedback.mdc
Follow Discord.py Components V2 rules from ui/cv2.mdc

Files:

  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/modules/moderation/jail.py
  • src/tux/database/controllers/guild_config.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/modules/moderation/clearafk.py
  • src/tux/services/handlers/error/cog.py
  • src/tux/services/moderation/moderation_coordinator.py
src/tux/modules/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/modules/**/*.py: Use hybrid commands (slash and traditional) for Discord bot commands
Implement role-based permissions for Discord commands
Implement cooldowns and rate limiting for Discord commands

Files:

  • src/tux/modules/moderation/unban.py
  • src/tux/modules/moderation/unjail.py
  • src/tux/modules/moderation/jail.py
  • src/tux/modules/moderation/slowmode.py
  • src/tux/modules/moderation/clearafk.py
src/tux/database/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/database/**/*.py: Use SQLModel for type-safe ORM operations
Use Pydantic for data validation in database models
Use transactions for multi-step database operations
Implement model-level validation in database models
Optimize database queries for performance

Files:

  • src/tux/database/controllers/guild_config.py
src/tux/services/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/tux/services/**/*.py: Use dependency injection in service classes
Keep service classes stateless where possible and use async/await for I/O
Use caching for frequently accessed data

Files:

  • src/tux/services/handlers/error/cog.py
  • src/tux/services/moderation/moderation_coordinator.py
🧠 Learnings (7)
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*interaction*.py : Follow interaction response patterns from `modules/interactions.mdc`

Applied to files:

  • src/tux/modules/moderation/jail.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to src/tux/modules/**/*.py : Implement cooldowns and rate limiting for Discord commands

Applied to files:

  • src/tux/modules/moderation/slowmode.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to **/*.py : Log with context when handling errors

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow logging patterns using loguru from `error-handling/logging.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow error patterns from `error-handling/patterns.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2026-01-12T20:42:11.193Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2026-01-12T20:42:11.193Z
Learning: Applies to **/*.py : Follow user-facing error message patterns from `error-handling/user-feedback.mdc`

Applied to files:

  • src/tux/services/handlers/error/cog.py
📚 Learning: 2025-12-30T22:45:12.307Z
Learnt from: CR
Repo: allthingslinux/tux PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T22:45:12.307Z
Learning: Applies to **/*.py : Use custom exceptions for business logic errors

Applied to files:

  • src/tux/services/handlers/error/cog.py
🧬 Code graph analysis (7)
src/tux/modules/moderation/unjail.py (1)
src/tux/modules/moderation/jail.py (2)
  • _respond (45-66)
  • get_jail_role (68-83)
src/tux/modules/moderation/jail.py (3)
src/tux/modules/moderation/unjail.py (2)
  • _respond (36-57)
  • get_jail_role (59-74)
src/tux/core/bot.py (2)
  • Tux (46-495)
  • is_jailed (193-217)
src/tux/modules/moderation/__init__.py (1)
  • is_jailed (111-133)
src/tux/database/controllers/guild_config.py (2)
src/tux/database/models/models.py (1)
  • GuildConfig (174-323)
src/tux/shared/cache.py (1)
  • GuildConfigCacheManager (170-246)
src/tux/modules/moderation/slowmode.py (1)
src/tux/modules/info/info.py (2)
  • _send_response (117-167)
  • info (342-377)
src/tux/modules/moderation/clearafk.py (2)
src/tux/modules/utility/afk.py (1)
  • afk (50-100)
src/tux/database/controllers/afk.py (1)
  • is_afk (142-151)
src/tux/services/handlers/error/cog.py (1)
src/tux/services/handlers/error/config.py (1)
  • ErrorHandlerConfig (38-60)
src/tux/services/moderation/moderation_coordinator.py (4)
src/tux/database/controllers/case.py (1)
  • create_case (80-198)
src/tux/services/moderation/case_service.py (1)
  • create_case (36-77)
src/tux/services/moderation/communication_service.py (1)
  • send_dm (41-89)
src/tux/database/models/models.py (1)
  • Case (569-726)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run All Tests (3.13.11)
  • GitHub Check: Validate
  • GitHub Check: Seer Code Review
🔇 Additional comments (25)
src/tux/modules/moderation/jail.py (2)

227-229: LGTM!

The conditional deferral logic correctly handles both slash and prefix commands—deferring only when an interaction exists, which avoids errors for prefix commands where ctx.interaction is None.


232-246: LGTM!

The error messages now route through _respond, providing consistent handling for both slash and prefix commands. This addresses the previous review feedback about inconsistent response methods.

src/tux/modules/moderation/unjail.py (3)

59-74: LGTM!

The type annotation update from Optional[discord.Role] to discord.Role | None aligns with the coding guidelines preference for the Type | None syntax.


196-198: LGTM!

Conditional deferral correctly handles both command invocation styles, consistent with the pattern in jail.py.


200-227: LGTM!

Error messages consistently use _respond for unified slash/prefix handling. The flow correctly validates jail role, jail status, and jail case before proceeding with the unjail operation.

src/tux/database/controllers/guild_config.py (3)

552-573: LGTM!

Good optimization to fetch both audit_log_id and mod_log_id in a single database query rather than making two separate calls via get_config_field. The docstring accurately describes the efficiency benefit.


143-159: LGTM!

Good refactor to delegate to update_config instead of calling update_by_id directly. This ensures cache invalidation is handled consistently when log channel fields are updated through this method.


12-16: LGTM!

Imports are correctly organized: stdlib (__future__, typing) → third-party (loguru) → local (tux.database.*, tux.shared.*).

src/tux/modules/moderation/clearafk.py (3)

39-58: LGTM!

The return type is now correctly discord.Message and the docstring accurately states it raises on send failure. This addresses the previous review feedback.


61-73: LGTM!

The early defer and interaction-aware response pattern is correctly implemented, consistent with the approach in afk.py and other modules in this PR. The assertion for type narrowing on followup.send is appropriate.


89-98: LGTM!

The success response follows the same interaction-aware pattern, correctly using followup.send for deferred interactions and falling back to ctx.send for prefix commands.

src/tux/modules/moderation/slowmode.py (5)

78-80: LGTM!

Early defer pattern correctly handles slash command interactions before async channel resolution work.


83-93: LGTM!

Channel resolution logic cleanly handles both argument patterns (delay-only vs channel+delay) with appropriate fallbacks.


119-159: LGTM!

Cache-first resolution pattern efficiently handles IDs and mentions without API calls, with appropriate converter fallback for name-based lookups.


231-260: LGTM!

Slowmode retrieval correctly checks channel capability and consistently uses _send_response for all output paths.


325-354: LGTM!

Delay parsing handles common time suffixes (s/m/h) correctly with appropriate error handling.

src/tux/services/handlers/error/cog.py (3)

128-129: Nice context propagation into logging.

Passing source into _log_error improves diagnosability without changing error flow.


183-233: Contextual logging and safe fallback look solid.

Good addition of command/guild/source context and a safe logger.error fallback.


253-255: Robust fallback logging for unexpected send failures.

logger.exception here is the right choice to preserve traceback.

src/tux/modules/moderation/unban.py (2)

159-163: LGTM!

The dual-path response pattern correctly handles both deferred interactions (using followup) and prefix commands (using reply). Consistent with the error handling at lines 144-151.


133-135: The success path properly handles deferred interactions. The send_embed method in the communication service explicitly checks if ctx.interaction: and uses ctx.interaction.followup.send() (line 220-221 of communication_service.py), making the response handling consistent with the error paths. No action needed.

Likely an incorrect or invalid review comment.

src/tux/services/moderation/moderation_coordinator.py (4)

153-225: Parallel case creation and expiry handling look solid.

Nice split of expiration derivation + async case creation while actions run; this keeps the flow responsive without losing case metadata.


380-399: Post-action DM timeout + cancellation handling is clean.

The explicit timeout and CancelledError handling improve resilience and user feedback.


401-446: Async case creation helper is well-factored.

Clear separation of concerns and logging make the parallel path easier to reason about.


529-538: Expiration field in mod-log embed is a nice UX win.

Clear, readable display of remaining time for temp actions.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants