Deployed commands
Control when your bot sends its commands to Discord and which servers get them, and see why seedcord dev sometimes skips the deploy.
Discord keeps its own copy of your commands, and your command folder is the source for it. A deploy overwrites Discord's copy.
Your bot sends one request per scope. A scope is the list of global commands, or the list of commands in one server. Discord replaces the whole scope with what arrives. If you delete a command file, Discord removes that command on the next deploy, unless it was the last one in its scope. A rename does the same to the old name.
When a deploy happens
| what runs | when it deploys |
|---|---|
| a started bot, gateway or http server | every scope that has commands, every time it starts |
seedcord dev | when the bot starts, and when you answer y to the refresh prompt after saving a command file. It skips a scope that hasn't changed |
| a deployed Cloudflare Worker | never |
seedcord commands deploy | when you run it and confirm |
seedcord build | never |
A started bot deploys on every start, and Discord rate-limits (opens in a new tab) replacing its global commands. In practice that's twice a minute, so a third restart inside that minute has to wait. The log says how many seconds Discord makes it wait. After 75 seconds startup stops waiting and fails.
The seedcord dev row depends on its refresh prompt, Refresh commands? (y/n), which shows when you save a command file. If you answer n, neither Discord nor the running bot receives the change, so restart the bot to load it.
The Worker row reads never because a Worker sends nothing to Discord. It reads the command ids that seedcord build wrote into it. Run seedcord commands deploy before you build a Worker. The build reads the ids from Discord and stops with a list of the commands that have none. Building a server bot never calls Discord, though starting it does. Workers covers the Worker build.
Test servers
A global command takes a while to appear, and a half-built command shouldn't reach every server your bot is in. To deploy every bare @RegisterCommand() command to your test servers only, list them under commands.guilds. The command appears there right away. Commands shows the config and the other decorator forms.
An empty list, or no guilds key, deploys those commands globally. A bot from create-seedcord reads the list from TESTING_GUILD_IDS in .env, a comma-separated list of ids.
Your bot has to be in every server on the list. If it isn't, the deploy fails.
In production
Run your production bot as its own application. If TESTING_GUILD_IDS is unset where it runs, its commands deploy globally when it starts. The test servers belong to your test application, so their copies never reach your users.
Deleting your last command leaves it live
A bot with only guild commands has an empty global list. Sending it would wipe the global commands that another deploy put there, so a bot never sends an empty global scope.
That means a deploy can't remove your last global command. Your bot sends nothing, so Discord keeps the copy it has. A guild scope with no commands isn't sent at start either.
seedcord dev treats a guild differently. If you delete the last command file for a server and answer y to the refresh prompt, dev sends that server an empty list, which clears it. The global scope is the only one dev never clears.
Adding another global command fixes the global scope on the next start. To empty a scope you can't refill, run seedcord commands purge for that scope, which seedcord commands explains.
seedcord dev skips a deploy that changes nothing
Every dev restart would otherwise replace every scope. That uses up the rate limit on commands that haven't changed.
After each deploy, dev saves one hash per scope in .seedcord/deployed-commands.json, filed under your bot's application id. The hash covers the whole definition of every command in the scope. On the next deploy, dev hashes each scope again. It skips a scope when two things hold:
- Its hash matches the file.
- The command names in it match the names Discord holds right now.
The second check reads Discord. If someone deleted or added a command there since your last deploy, the names differ, so dev deploys the scope.
Skipped the global commands because they match the last deploy.When the log shows that line, dev compared only names. Say you deployed other versions of the same commands from another checkout. The names match, so your dev run skips them and Discord keeps the other versions. Run seedcord commands deploy --force to deploy anyway, or delete .seedcord/deployed-commands.json.
seedcord commands deploy writes the same file. If you deploy from the CLI, the next seedcord dev run skips whatever you just sent. A started bot reads no file and deploys every scope that has commands.
When Discord refuses a deploy
The bot fails to start, and the error shows Discord's reply. A 401 means DISCORD_BOT_TOKEN is wrong or was reset. A 400 means a command broke one of Discord's rules for names, descriptions or options, and Discord's reply says which field.
Discord refused the global command deploy
If Discord answered with a status other than 401 or 400, try again. If it keeps failing, check discordstatus.com.
Discord refused the command deploy for guild
The error includes the guild id. If Discord answered with a status other than 401 or 400, check that id under commands.guilds or in your decorator, and check that your bot is in that server.