diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index 69b1bf4d228..8146ca9de10 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -31,6 +31,12 @@ _DISCORD_COMMAND_SYNC_STATE_SUBDIR = "gateway" _DISCORD_COMMAND_SYNC_STATE_FILENAME = "discord_command_sync_state.json" _DISCORD_COMMAND_SYNC_MUTATION_INTERVAL_SECONDS = 4.5 _DISCORD_COMMAND_SYNC_MAX_RATE_LIMIT_SLEEP_SECONDS = 30.0 +# Discord enforces a hard cap of 100 global application (slash) commands per +# app. Registering more makes the ENTIRE sync fail with error 30032 +# ("Maximum number of application commands reached"), which silently breaks +# every slash command — not just the overflow ones. We keep the desired set +# at or below this limit at registration time. +_DISCORD_MAX_APP_COMMANDS = 100 try: import discord @@ -3518,6 +3524,11 @@ class DiscordAdapter(BasePlatformAdapter): ) already_registered: set[str] = set() + # Native commands above are registered first and are the highest + # priority, so they always survive the 100-command cap. Reserve one + # slot for the consolidated ``/skill`` group registered further below. + slot_cap = _DISCORD_MAX_APP_COMMANDS - 1 + dropped_over_cap = 0 try: from hermes_cli.commands import COMMAND_REGISTRY, _is_gateway_available, _resolve_config_gates @@ -3535,6 +3546,9 @@ class DiscordAdapter(BasePlatformAdapter): discord_name = cmd_def.name.lower()[:32] if discord_name in already_registered: continue + if len(already_registered) >= slot_cap: + dropped_over_cap += 1 + continue auto_cmd = _build_auto_slash_command( cmd_def.name, cmd_def.description, @@ -3567,6 +3581,9 @@ class DiscordAdapter(BasePlatformAdapter): discord_name = plugin_name.lower()[:32] if discord_name in already_registered: continue + if len(already_registered) >= slot_cap: + dropped_over_cap += 1 + continue auto_cmd = _build_auto_slash_command( plugin_name, plugin_desc, @@ -3589,6 +3606,20 @@ class DiscordAdapter(BasePlatformAdapter): # supporting up to 25 categories × 25 skills = 625 skills. self._register_skill_group(tree) + if dropped_over_cap: + # Staying under the cap keeps the whole sync succeeding; without + # this guard a single over-limit command makes Discord reject the + # entire batch (error 30032), breaking every slash command. + logger.warning( + "[%s] Reached Discord's limit of %d slash commands; skipped %d " + "lower-priority command(s) to keep the command sync working. " + "Disable slash commands you don't need or trim installed plugins " + "to surface them all.", + self.name, + _DISCORD_MAX_APP_COMMANDS, + dropped_over_cap, + ) + # Optional defense-in-depth: hide every slash command from non-admin # guild members in Discord's slash picker. Server-side authorization # (``_check_slash_authorization``) is the actual gate; this is purely