Discord bot not responding but still running: how to catch it
Your bot shows as online in Discord. The process is there in ps. And it answers nothing.
This is the failure that gets past every restart-if-it-dies setup, because nothing died. Here is how to detect it and what to do about it.
Why your supervisor misses this
launchd with KeepAlive, systemd with Restart=always, pm2, a while loop that checks for the process — they all watch the same thing. Is the process there?
When a session expires, a daily quota runs out, or a request hangs on a socket with no timeout, the process is still there. It holds memory, it shows up in the process list, and the supervisor is satisfied. The bot is dead in every way that matters and nothing will restart it.
Signals that lie to you
Before building a check, know which signals are useless here.
- Discord shows the bot online. The gateway connection can outlive the part of your code that handles messages
- The process exists. That is exactly the thing that is wrong
launchctl listshows a PID. Same problem- Logs are quiet. A stuck bot usually stops logging, and "no errors" reads like "no problems"
- A screen dump is empty. If your bot draws a text interface,
screen -X hardcopywrites an empty file. We tried to use it for health checks and wasted time on it
Watch work, not life
The check that works is the boring one. Have the bot record when it last did something real.
Not when it started. Not when it received a message. When it finished handling one.
from pathlib import Path
import time
HEARTBEAT = Path("/tmp/mybot.alive")
async def on_message(msg):
... # handle the command
await msg.channel.send(reply)
HEARTBEAT.write_text(str(int(time.time()))) # only after a real reply
Then a separate script decides whether that timestamp is too old.
now=$(date +%s) last=$(cat /tmp/mybot.alive 2>/dev/null || echo 0) if [ $((now - last)) -gt 900 ]; then pkill -f mybot.py # let the supervisor bring it back fi
Killing it is the whole fix. You do not need restart logic — whatever already restarts a dead process will handle it. You are just converting an invisible failure into a visible one.
Choosing the threshold
Too short and you restart a bot that is merely idle. Too long and you are down for hours.
The number depends on traffic, not on taste. If your bot handles something every few minutes, fifteen minutes is generous. If it goes quiet overnight, a timestamp alone will produce false restarts — in that case have the bot do a cheap internal task on a timer and stamp that, so the heartbeat reflects the bot being capable, not being used.
Keep the watcher light
This one cost us eight hours.
Our watchdog checked connectivity before restarting the bot, and that check fetched a page — the whole 170KB body. On a slow connection it exceeded its own timeout, decided the network was down, and refused to restart. The bot was dead the entire time and the watchdog was the reason it stayed dead.
Switching to a HEAD request took it to 0.5 seconds.
If a watchdog can fail, it will fail at the worst time. Keep every check in it cheap and give each one a timeout shorter than the loop interval.
A quick audit
Answer these about your own setup:
1. If the process froze right now, what would notice? If the answer is "me, eventually", you have this problem
2. Does your health check look at anything other than the process existing?
3. Can your watchdog itself hang? What is its slowest operation?
Related
- The setup this runs on: Run a Discord bot 24/7 for free on a computer you already own
- Environment traps when starting from a supervisor: mac environment variables that autostart does not have
Every number here is from a bot that has been running on the same machine for 34 days.
Comments