Debugging
Debug and troubleshoot your bot
Robo.js provides built-in tools for debugging your bot, including test server scoping, a /dev command, and structured logging.
For plugin-specific troubleshooting including common errors, registration issues, and interaction timing, see the @robojs/discordjs Troubleshooting guide.
Test Server
Set DISCORD_GUILD_ID in your environment variables to scope command registration to a single server during development. Guild-scoped commands update instantly, while global commands can take up to an hour to propagate.
import type { DiscordConfig } from '@robojs/discordjs'
export default {
testServers: [process.env.DISCORD_GUILD_ID]
} satisfies DiscordConfigexport default {
testServers: [process.env.DISCORD_GUILD_ID]
}This keeps development commands out of production servers and provides immediate feedback when iterating on command changes.
The /dev Command
A built-in /dev command is available when running in development mode. It displays uptime, memory usage, registered commands, loaded plugins, and recent logs directly in Discord. This command does not appear in production.
The /dev command only appears when running in development mode (npx robo dev).
Discord showing the output of the /dev command displaying an embed with uptime, memory usage, registered commands count, loaded plugins, and recent log entries
Logging
Use the logger from robo.js/logger.js for structured log output. Log levels control verbosity and can be configured per environment.
import { logger } from 'robo.js/logger.js'
export default () => {
logger.info('Command executed')
logger.debug('Processing data...')
return 'Done!'
}import { logger } from 'robo.js/logger.js'
export default () => {
logger.info('Command executed')
logger.debug('Processing data...')
return 'Done!'
}| Level | Method | Description |
|---|---|---|
| trace | logger.trace() | Verbose tracing |
| debug | logger.debug() | Debug information |
| info | logger.info() | General information |
| warn | logger.warn() | Warnings |
| error | logger.error() | Errors |
Forked Loggers
Create named logger instances with logger.fork(). This adds a prefix to all log output, making it easier to filter logs by source. Forked loggers are useful for both plugin authors and large bots where different parts of the application need to stand out in logs (e.g., logger.fork('payments'), logger.fork('auth')).
import { logger } from 'robo.js/logger.js'
export const paymentLogger = logger.fork('payments')
paymentLogger.info('Processing refund')
// Output: [payments] Processing refundimport { logger } from 'robo.js/logger.js'
export const paymentLogger = logger.fork('payments')
paymentLogger.info('Processing refund')
// Output: [payments] Processing refundTerminal output showing structured Robo.js logger output with different log levels in distinct colors, plus a forked logger message prefixed with [payments]
Error Handling
Always use try/catch with async operations inside handlers. Unhandled errors in event handlers can cause unexpected behavior or crash your bot.
import { logger } from 'robo.js/logger.js'
export default async () => {
try {
const data = await fetchExternalApi()
return `Result: ${data}`
} catch (error) {
logger.error('API call failed:', error)
return 'Something went wrong.'
}
}import { logger } from 'robo.js/logger.js'
export default async () => {
try {
const data = await fetchExternalApi()
return `Result: ${data}`
} catch (error) {
logger.error('API call failed:', error)
return 'Something went wrong.'
}
}Unhandled promise rejections in event handlers can crash your bot. Always use try/catch with async operations.
Debug Channel
Set the DISCORD_DEBUG_CHANNEL_ID environment variable to forward background errors to a specific Discord channel. When configured, Sage sends error details including stack traces and context to this channel, making it easier to monitor issues in production without checking server logs.
Using the Debug Channel Effectively
- Create a private channel in your server (e.g.,
#bot-errors) visible only to developers. - Copy the channel ID (enable Developer Mode in Discord settings, then right-click the channel).
- Add it to your
.envfile:
# .env
DISCORD_DEBUG_CHANNEL_ID="your_channel_id"Errors that occur outside of command or event handlers (background tasks, unhandled rejections) are forwarded here with full stack traces and a time-locked log snapshot showing what happened leading up to the error.
Discord channel #bot-errors showing a Sage error report embed with error details, stack trace, file path, and log snapshot section forwarded from a background error
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
TOKEN_INVALID | Bot token is incorrect or expired | Regenerate the token in the Discord Developer Portal under Bot > Reset Token and update your .env |
Missing Access | Bot lacks permissions in the channel | Check that the bot role has the required permissions for the channel. Re-invite with correct permissions if needed |
Unknown Command | Command not registered with Discord | Run npx robo build --force to force re-registration. Check that the file exists in src/commands/ |
Missing Intent | Required gateway intent not enabled | Enable the intent in the Developer Portal under Bot > Privileged Gateway Intents and add it to your plugin config |
DISALLOWED_INTENTS | Using a privileged intent without portal approval | Enable the specific intent in the Developer Portal. Bots in 100+ servers must be verified first |
Used disallowed intents | Same as above (Discord.js error variant) | Same fix as DISALLOWED_INTENTS |
Interaction has already been acknowledged | Calling reply() or deferReply() twice | When using Sage mode, do not call interaction.reply() manually. Either return a value (Sage) or use interaction.reply() exclusively |
Command Registration Issues
When commands do not appear in Discord or appear outdated, the issue is usually related to registration caching.
Force Re-registration
Robo.js uses hash caching to detect command changes and only re-registers when files have changed. If commands are not updating or you see stale commands that should have been removed, force a full re-registration:
npx robo build --forceThe --force flag bypasses the hash cache, re-registers all commands with Discord, and cleans up commands that no longer exist in your src/commands/ directory.
Guild vs Global Commands
Guild-scoped commands (when DISCORD_GUILD_ID is set) update instantly. Global commands can take up to an hour to propagate across all servers. During development, always use guild-scoped registration for fast feedback.
Mock Server
For automated testing, see Mock Server which simulates Discord's API locally. This lets you test commands, events, and interactions without a live Discord connection.
