FAQ
The questions that come up most often, grouped by topic. Each answer points at the deeper page if you need the full story.
Getting it running
My bot doesn’t come online — there’s no green dot.
Three common causes, in order of frequency:
- The token is wrong. Logs show
Invalid Tokenor a WebSocket close. Generate a new token in the Discord Developer Portal under your application → Bot → Reset Token, paste it into your.env, restart the bot. - Privileged intents are off. The bot needs Message Content
Intent (to read prefix commands) and Server Members Intent
(for joins, auto-role, welcome). Both are toggles on the same Bot
page. Logs show
Disallowed intents. - The bot panicked at startup. Look for the line right before
the process exits. The
Config::load()panics with a clear message when a required env var is missing or still has its placeholderyour-...value.
See Quickstart Troubleshooting for the full list and Debugging for log-level controls.
!m help doesn’t get a reply, but the bot is online.
Two possibilities:
- Permissions in that channel. The bot needs View Channel, Send Messages, and Read Message History. Some channels inherit deny-by-default overrides that block bot replies. Check channel-level overrides for the bot’s role.
- The prefix is wrong. Default is
!, butcommand_prefixinconfig.tomlmay have been changed. The startup logs printInstance config loaded: <name> (prefix: <prefix>)— check that.
AI chat doesn’t reply when I @mention the bot.
AI chat needs at least one of DEEPSEEK_API_KEY or GEMINI_API_KEY.
If neither is set, mentions are silently ignored. Set one in your
.env, restart, and try again.
If you have a key set and it still doesn’t reply, tail the logs with
RUST_LOG=discord_bot::ai=debug,info — the pipeline logs every
inbound mention and every API call. Common downstream causes:
- Rate limit hit (10 calls per user per 60s). Wait a minute.
- DeepSeek/Gemini outage. The logs will show the upstream error.
- The bot lacks Send Messages in the channel you mentioned it in.
See AI Chat for the full feature page.
Music doesn’t play.
The pipeline is yt-dlp → ffmpeg → songbird. Each can fail:
yt-dlpoutdated — YouTube breaks yt-dlp every few weeks.pip install -U yt-dlpin the bot’s environment, restart. The Dockerfile pins topip install yt-dlp; rebuilding the image pulls the latest.ffmpegmissing onPATH— the Docker image bundles it; bare-metal needsapt install ffmpeg.- Bot can’t join voice — check the voice channel’s permissions for Connect and Speak on the bot’s role.
- DJ mode is on —
!m djmodetoggles a setting where only members with the configured DJ role can use music commands. Check with!m djmodeagain to see the current state.
Database connection issues.
Failed to connect to database at startup means the connection
string in .env is wrong or the database isn’t reachable. Quick
sanity check: psql "$DATABASE_URL" from the same host. If that
works and the bot still can’t connect, double-check the bot is
loading the right .env (CONFIG_DIR must point at the directory
holding it).
If you see pool acquire timed out mid-run, Postgres restarted or
the network blipped. sqlx reconnects on the next query — usually no
action needed, but check Postgres health if it keeps happening.
Multi-instance and operations
How do I run more than one bot on the same machine?
The model is “one process per bot, one schema per process, all
sharing one Postgres.” Concretely: copy instances/example/ to a
new directory, fill in a different DISCORD_TOKEN, CLIENT_ID,
GUILD_ID, and DB_SCHEMA, then add a second bot service to
docker-compose.yml pointing at the new directory.
The full recipe is in
Multiple Instances. The
key constraint: DB_SCHEMA must be unique per instance. There’s
no defensive check, and two instances on the same schema will
corrupt each other’s data.
How do I upgrade?
Pull the latest image (or rebuild from source), then restart the bot service:
git pull
docker compose pull bot
docker compose up -d bot
The bot is forward-compatible across schema migrations — migrate
runs CREATE TABLE IF NOT EXISTS for every table at startup. If a
release introduces a destructive migration (column rename, table
drop) the changelog and release notes will say so explicitly.
For multi-instance deployments, restart instances one at a time so you keep at least one bot online while you upgrade the others.
See Upgrading for the deeper version of this answer including rollback steps.
How do I back up the bot?
Two things to back up:
- The Postgres data volume. All persistent state — tempbans,
guild settings, stock portfolios, member activity, reminders if
you’ve added them — lives there.
pg_dumpagainst yourDATABASE_URL, store the dump somewhere safe, automate with cron or a managed backup service. Per-instance dumps arepg_dump --schema=<DB_SCHEMA>. - Your
instances/directories. They contain.env(with secrets),config.toml,personality.txt, and any prompt files you’ve added.tarthem up and store alongside the database backups. They’re small.
Source code is on GitHub; you don’t need to back that up.
How do I expose the MCP server externally?
Default behaviour is MCP_BIND_ADDR=127.0.0.1, which only the bot’s
own host can reach. To expose it:
- Set
MCP_AUTH_TOKEN=<long random value>— without auth on a public address, anyone with network access can drive the bot. - Set
MCP_BIND_ADDR=0.0.0.0(or your specific interface). - Open the port (
MCP_PORT, default9090) in your firewall. - If you’re running multiple instances, use the included
mcp-gatewayto route requests by instance name.
MCP Exposure walks through the threat model. Don’t skip the auth token.
How do I enable Minecraft features?
Three steps:
- Install the companion plugin on your Minecraft server (it owns the verification and donator-tier endpoints the bot calls).
- Set
MC_VERIFY_URLandMC_VERIFY_SECRETin the bot’s.env. - In
config.toml, setfeatures.minecraft = trueand toggle the sub-features you want under[minecraft]:verify,donator_sync,chargeback. Each sub-feature has its own[minecraft.*_config]table — seeinstances/example/config.tomlfor every option.
Restart the bot. The startup logs print which sub-features activated and which are enabled-but-misconfigured.
Minecraft Verify, Donator Sync, and Chargeback Alerts have the deeper details.
Design and conventions
Why prefix commands and no slash commands?
Three reasons, in order:
- Iteration speed. Prefix commands are immediate — change a handler, rebuild, type the command, see the result. Slash commands have global registration delays and per-guild quotas that turn the dev loop into “edit, push, wait, test.”
- No global state in Discord. Slash commands live as objects in Discord’s API, attached to your application. Prefix commands live entirely in your code. That makes the bot easier to fork, self-host, and run with custom variants.
- Subcommand parsing was already solved. Poise gives the bot a
parent
mcommand with a typed subcommand tree, which gives users an interface that reads like!m play,!m wordle,!m stocks portfolio— close enough to slash UX to be familiar without inheriting the registration pain.
The prefix is configurable per-instance (command_prefix in
config.toml); pick whatever you want.
What’s the license, really?
AGPL-3.0-or-later. The shape of it for a bot host:
- You can run this bot for any purpose, commercial or not.
- You can modify it for your own use without telling anyone.
- If you run a modified version that interacts with users over a network — which a Discord bot does by definition — the AGPL obligates you to make your modified source available to those users on request.
- You can’t take this code, modify it, run it as a service, and refuse to share the source. That’s the entire point of AGPL over GPL.
Contributing back is welcome but not required by the license; the license only requires that derivative network services share their own source. See CONTRIBUTING.md for the contribution terms.
Can I commercialize this?
Yes, with the AGPL constraint above. You can run a paid hosted version of this bot, charge money for support, or sell custom features built on top — the license imposes no fee, no royalty, and no commercial restriction. What it does require: anyone using your hosted version can request the source code of the version you’re running, and you have to provide it. If you’re building proprietary extensions that you don’t want to release, the AGPL probably isn’t compatible with your business model and you should look elsewhere.
How do I contribute?
The short version: fork, branch, change, test, PR. The long version
is in Contributing Workflow.
The pre-PR checklist is cargo fmt,
cargo clippy --all-targets -- -D warnings, cargo test, and a
manual run-through in a test Discord server.
If you’re new to the codebase, start with Codebase Tour and pick a small issue from GitHub Issues to land your first PR.
Misc
What models does the AI use?
The default is DeepSeek’s deepseek-v4-flash model when
DEEPSEEK_API_KEY is set, falling back to Google’s Gemini
(gemini-2.0-flash) when DeepSeek errors out and GEMINI_API_KEY
is set. Both are remote API calls; the bot has no local model.
DeepSeek is recommended as the primary because it’s the cheapest of
the supported providers.
Can I change the bot’s personality?
Yes — every instance has a personality.txt next to its
config.toml. The contents are used as the system prompt for AI
chat. Edit, restart the bot, talk to it. See
Personality Files for tips on
writing one that holds up.
Where does the music come from?
Anything yt-dlp can resolve — YouTube videos, playlists,
SoundCloud, Bandcamp, direct links to media files, and a few
hundred other sources. yt-dlp does the resolution; ffmpeg pipes the
output to songbird in OGG/Opus passthrough so the bot doesn’t
re-encode. The pipeline is in src/music/track.rs and voice.rs.
Why does the bot use so much disk space?
It doesn’t, unless logging is misconfigured. The bot itself stores
nothing on disk other than personality.txt and any cookies
yt-dlp keeps in its cache. All persistent state is in Postgres.
If your container is growing, check docker logs --no-color bot | wc -l — Docker keeps logs forever by default. Set a size limit in
docker-compose.yml under the bot service:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Still stuck?
Log a GitHub Issue. Bug report template asks for the version, reproduction steps, and redacted logs — those three together usually let someone help you on the first reply.