Discord Platform Expert
Load Order
Read shared-kernel/SKILL.md first.
Core Competencies
Gateway
- Gateway v10+, intent bitfields, privileged intents (GUILD_MEMBERS, MESSAGE_CONTENT, GUILD_PRESENCES)
- Resume vs reconnect semantics, session invalidation handling
- Sharding:
(guild_id >> 22) % num_shards, required at 2500+ guilds
- Heartbeat cadence, zombie connection detection, exponential reconnect backoff
Interactions API
- Slash commands (global vs guild scope, cache propagation ~1 hour global, instant guild)
- Context menu commands (user, message)
- Buttons, select menus (string, user, role, channel, mentionable), modals, text inputs
- Components v2: sections, containers, media galleries, thumbnails, separators
- Autocomplete: 25 choice limit, 3s response window, no embeds/files
- Interaction token lifecycle: 3s initial ack required, 15-minute follow-up window
deferReply({ ephemeral: true }) before long-running work
editReply vs followUp vs update — know which one applies
Rate Limits
- Per-route buckets + global 50 req/s limit
- Respect
X-RateLimit-Remaining, X-RateLimit-Reset-After, Retry-After
- Handle 429 with
retry_after from response body, not just headers
- Invalid request limit: 10,000 per 10 minutes before temp ban
OAuth2
- Scopes:
bot, applications.commands, identify, guilds, guilds.join, email
- Permission integer calculation — use bitfield constants, never magic numbers
- PKCE for public clients, state parameter for CSRF protection
- Refresh token rotation
Voice
- Voice gateway v8, UDP + RTP, Opus encoding (48kHz, 2 channels, 20ms frames)
- Voice state updates, speaking indicators
- DAVE protocol for E2EE (newer client versions)
Version Verification (Required First Step)
Before writing any Discord code, confirm:
npm ls discord.js / pip show discord.py / dotnet list package
- Library major version (discord.js v14 ≠ v13 — breaking API differences)
- Which intents are declared vs which endpoints are actually called
- Whether the bot is verified (required for >100 guilds with privileged intents)
Common Failure Modes
| Symptom |
Root Cause |
message.content is empty |
Missing MESSAGE_CONTENT privileged intent |
guild.members.cache is small |
Missing GUILD_MEMBERS intent or no fetch() call |
| "This interaction failed" |
Did not respond within 3 seconds — needs deferReply() |
| Token in source control |
Immediate security escalation — rotate and flag |
| Commands not appearing |
Global deploy takes up to 1 hour; use guild deploy for dev |
DISALLOWED_INTENTS on identify |
Requested privileged intent not enabled in Developer Portal |
| Bot in >2500 guilds, single shard |
Sharding required; Discord will force-disconnect |
| Rate limit on webhook |
Per-channel bucket, not per-webhook — throttle per channel |
Non-Negotiables
- Tokens live in environment variables or a secret manager, never in source
- Every interaction handler calls
deferReply() if work takes >2s
- Every command has permission checks before side effects
- Every database write triggered by a command is idempotent or deduplicated
- Graceful shutdown on SIGTERM — finish in-flight interactions, close gateway cleanly
Deliverables
Production Bot Scaffold (discord.py reference shape)
import os
import signal
import logging
import asyncio
import discord
from discord.ext import commands
logger = logging.getLogger(__name__)
intents = discord.Intents.default()
intents.message_content = True # declare only what is used
intents.members = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
logger.info("Connected as %s (id=%s)", bot.user, bot.user.id)
await bot.tree.sync() # or sync to guild in dev
async def shutdown():
logger.info("Shutdown signal received")
await bot.close()
def main():
token = os.environ["DISCORD_TOKEN"] # fail fast if missing
loop = asyncio.new_event_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown()))
loop.run_until_complete(bot.start(token))
if __name__ == "__main__":
main()
Command Handler Pattern
@bot.tree.command(name="status", description="Check service status")
@discord.app_commands.checks.has_permissions(manage_guild=True)
async def status(interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True)
result = await fetch_status() # may take > 3s
await interaction.followup.send(embed=build_status_embed(result))
Reference Links to Verify
1---2name: discord-platform-expert3description: Discord Platform Expert4---56# Discord Platform Expert78## Load Order9Read `shared-kernel/SKILL.md` first.1011## Core Competencies1213### Gateway14- Gateway v10+, intent bitfields, privileged intents (GUILD_MEMBERS, MESSAGE_CONTENT, GUILD_PRESENCES)15- Resume vs reconnect semantics, session invalidation handling16- Sharding: `(guild_id >> 22) % num_shards`, required at 2500+ guilds17- Heartbeat cadence, zombie connection detection, exponential reconnect backoff1819### Interactions API20- Slash commands (global vs guild scope, cache propagation ~1 hour global, instant guild)21- Context menu commands (user, message)22- Buttons, select menus (string, user, role, channel, mentionable), modals, text inputs23- Components v2: sections, containers, media galleries, thumbnails, separators24- Autocomplete: 25 choice limit, 3s response window, no embeds/files25- Interaction token lifecycle: **3s initial ack required**, 15-minute follow-up window26- `deferReply({ ephemeral: true })` before long-running work27- `editReply` vs `followUp` vs `update` — know which one applies2829### Rate Limits30- Per-route buckets + global 50 req/s limit31- Respect `X-RateLimit-Remaining`, `X-RateLimit-Reset-After`, `Retry-After`32- Handle 429 with `retry_after` from response body, not just headers33- Invalid request limit: 10,000 per 10 minutes before temp ban3435### OAuth236- Scopes: `bot`, `applications.commands`, `identify`, `guilds`, `guilds.join`, `email`37- Permission integer calculation — use bitfield constants, never magic numbers38- PKCE for public clients, state parameter for CSRF protection39- Refresh token rotation4041### Voice42- Voice gateway v8, UDP + RTP, Opus encoding (48kHz, 2 channels, 20ms frames)43- Voice state updates, speaking indicators44- DAVE protocol for E2EE (newer client versions)4546## Version Verification (Required First Step)47Before writing any Discord code, confirm:48- `npm ls discord.js` / `pip show discord.py` / `dotnet list package`49- Library major version (discord.js v14 ≠ v13 — breaking API differences)50- Which intents are declared vs which endpoints are actually called51- Whether the bot is verified (required for >100 guilds with privileged intents)5253## Common Failure Modes5455| Symptom | Root Cause |56|---|---|57| `message.content` is empty | Missing `MESSAGE_CONTENT` privileged intent |58| `guild.members.cache` is small | Missing `GUILD_MEMBERS` intent or no `fetch()` call |59| "This interaction failed" | Did not respond within 3 seconds — needs `deferReply()` |60| Token in source control | Immediate security escalation — rotate and flag |61| Commands not appearing | Global deploy takes up to 1 hour; use guild deploy for dev |62| `DISALLOWED_INTENTS` on identify | Requested privileged intent not enabled in Developer Portal |63| Bot in >2500 guilds, single shard | Sharding required; Discord will force-disconnect |64| Rate limit on webhook | Per-channel bucket, not per-webhook — throttle per channel |6566## Non-Negotiables67- Tokens live in environment variables or a secret manager, never in source68- Every interaction handler calls `deferReply()` if work takes >2s69- Every command has permission checks before side effects70- Every database write triggered by a command is idempotent or deduplicated71- Graceful shutdown on SIGTERM — finish in-flight interactions, close gateway cleanly7273## Deliverables7475### Production Bot Scaffold (discord.py reference shape)7677```python78import os79import signal80import logging81import asyncio82import discord83from discord.ext import commands8485logger = logging.getLogger(__name__)8687intents = discord.Intents.default()88intents.message_content = True # declare only what is used89intents.members = True9091bot = commands.Bot(command_prefix="!", intents=intents)9293@bot.event94async def on_ready():95 logger.info("Connected as %s (id=%s)", bot.user, bot.user.id)96 await bot.tree.sync() # or sync to guild in dev9798async def shutdown():99 logger.info("Shutdown signal received")100 await bot.close()101102def main():103 token = os.environ["DISCORD_TOKEN"] # fail fast if missing104 loop = asyncio.new_event_loop()105 for sig in (signal.SIGINT, signal.SIGTERM):106 loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown()))107 loop.run_until_complete(bot.start(token))108109if __name__ == "__main__":110 main()111```112113### Command Handler Pattern114115```python116@bot.tree.command(name="status", description="Check service status")117@discord.app_commands.checks.has_permissions(manage_guild=True)118async def status(interaction: discord.Interaction):119 await interaction.response.defer(ephemeral=True)120 result = await fetch_status() # may take > 3s121 await interaction.followup.send(embed=build_status_embed(result))122```123124## Reference Links to Verify125- https://discord.com/developers/docs (primary source of truth)126- Library changelog for the specific version in use127- Discord API server announcements channel for deprecations