Why Is My Bot Replying Twice? Four Causes, in Order of Likelihood
A bot that answers every command twice is almost always two copies of the bot running at once, each receiving its own stream of events and each replying. The usual culprit isn't your code — it's the old host you migrated away from and left running, a staging bot sharing the production token, or a second app you forgot about. The remaining case is a code bug where the same handler gets registered more than once. One thing you can rule out immediately: a redeploy here can't cause it, because the old container is stopped and removed before the new one is created.
First, rule out the platform
On some hosts a deploy briefly runs old and new versions side by side, and for a bot that means doubled replies during the overlap. That's not what happens here.
A redeploy stops and removes the old container before creating the new one — nothing overlaps. So if your bot is doubling, it isn't the deploy, and you can stop looking there. That's genuinely useful information, because it eliminates the explanation people reach for first.
Cause 1: the old host is still running
This is the most common one by a distance, and it's a direct consequence of sensible migration advice.
When you move a bot from Replit, Heroku, a VPS, or your own laptop, the natural instinct is to leave the old one in place for a few days "just in case." If the old copy is still running with the same token, both bots connect, both receive every event, and both reply.
So the advice needs one word added: stop the bot on the old host before you start it on the new one. Keep the project, keep the files, keep the ability to go back — but don't leave the process running. If you're following the move-off-Replit or Heroku migration route, that's the step to do first, not last.
Things that count as "the old host" and get forgotten:
- A repl or dyno you stopped paying attention to but not paying for.
- A
python bot.pystill running in a terminal on your laptop or a VPSscreensession. - A systemd service on a server you've stopped thinking about.
Cause 2: a staging bot with the production token
If you set up a staging bot by copying the app and forgetting to change the token, you now have two production bots. They're in the same servers, both see every message, and both answer.
The fix is the same as the rule: a staging bot needs its own bot token, not just its own app. A separate application in the developer portal, invited only to a private test server. If it shares the token, it isn't staging — it's a second production instance with a different name in your dashboard.
Cause 3: a second app you forgot
Worth checking mechanically rather than from memory. Open your Applications list and look for anything running the same code — an experiment from months ago, an app you renamed and lost track of, a copy created while debugging a deploy.
Because the number of apps doesn't affect your bill, a forgotten app doesn't show up as a cost surprise the way it would elsewhere. It just quietly answers your users twice.
If you find one and aren't sure whether you need it, stop it rather than deleting it. A stopped app keeps its configuration and volume.
Cause 4: the same handler registered twice
If you've confirmed only one instance is running, it's your code — and there's one pattern that causes this repeatedly.
Registering handlers inside a lifecycle event. In discord.py, on_ready can fire more than once — it runs again after a reconnect, and gateway reconnects are normal. If you register commands or event handlers inside it, each reconnect adds another copy, and the bot replies once per registration:
@bot.event
async def on_ready():
bot.add_listener(on_message) # adds another listener every reconnect
Register handlers once at module level or during setup, not inside a handler that can re-fire. The tell for this cause is that duplication appears after the bot has been running a while, or gets worse over time — one reply, then two, then three — rather than being doubled from the moment it starts.
Two instances double from the start and stay doubled. Re-registration grows. That difference identifies the cause faster than any log.
Telegram specifics
For Telegram bots there's one extra thing to know: polling and webhooks are mutually exclusive. Telegram's own documentation states you won't receive updates via getUpdates while an outgoing webhook is set. So you can't accidentally get double delivery from having both — but you can end up with a bot that receives nothing because a webhook you forgot is still registered.
Check getWebhookInfo if updates stop arriving after you switch modes. The duplicate case for Telegram is the same as Discord's: two processes polling with the same token.
A three-minute diagnosis
- Does it double from the very first message after a restart? → two instances. Go to cause 1–3.
- Did it start doubling later, or is it now tripling? → handler re-registration. Go to cause 4.
- Add a startup log line with a random ID. Restart and watch — if two different IDs appear in your logs, or if one reply comes from an instance whose ID isn't in your logs at all, you've found your second copy.
That third one is the definitive test. A bot instance you can't see logs for is a bot instance running somewhere else.
FAQ
Why does my Discord bot respond to every command twice?
Almost always two copies running with the same token — commonly the old host after a migration, a staging bot sharing the token, or a forgotten app. Each connection receives every event independently.
Can a deploy cause duplicate replies?
Not here. The old container is stopped and removed before the new one starts, so two versions never run simultaneously.
How do I tell two instances from a code bug?
Two instances double immediately and consistently. Re-registered handlers cause duplication that appears later or increases over time.
Why does on_ready cause duplicate handlers?
It can fire again after a gateway reconnect. Anything you register inside it gets registered again each time. Register at setup instead.
Can a Telegram bot get double delivery from webhook and polling?
No — getUpdates returns nothing while a webhook is set. The risk there is receiving nothing, not receiving twice. Two polling processes will duplicate.
How do I find the other instance?
Log a random ID at startup. If replies come from an instance whose ID never appears in your logs, it's running somewhere you're not looking.
Wrapping up
Duplicate replies feel like a mysterious code problem and are usually an inventory problem: something is running that you forgot about. The habit that prevents it is boring and effective — stop the old thing before you start the new one, give every non-production copy its own token, and log an instance ID at startup so "how many of me are there?" is a question your logs can answer.