Bot Keeps Restarting but Shows as Running? Read the Exit Code

·6 min read·Updated
discordtelegrambotsdebugginglogshow-to

If your bot keeps restarting but the dashboard says it's running, both things are true. When you deployed by uploading a zip, your process is supervised by a loop that starts it, waits for it to exit, prints the exit code, sleeps five seconds, and starts it again — forever. The container itself never stops, so nothing turns red and no restart counter goes up. The signal you want isn't the status, it's a line in the logs: !!! Process exited with code '<n>'. Restart after 5 sec. If that repeats every five seconds, your bot is crash-looping, and the exit code tells you why.

Why the status lies

Most container platforms decide health from the container: if the process dies, the container exits, the restart counter increments, and the status goes red.

Here, if the image was built for you from a zip upload, the process is wrapped in a supervisor. Roughly:

while true; do
    <your command>
    echo "!!! Process exited with code '$?'. Restart after 5 sec"
    sleep 5
done

That's a deliberate trade — a transient failure recovers on its own instead of leaving your bot dead until you notice — but it means the container is healthy by definition. It's doing exactly what it was told, which happens to be "restart this broken thing forever."

So never conclude a bot is fine because its status is green. Open the logs and look for that exit line. System messages from the supervisor are prefixed [System], so they're easy to pick out from your own output.

If you brought your own image

Deploy with your own Dockerfile — via git push or Docker push — or from an existing image, and your ENTRYPOINT runs directly with no wrapper around it. There's no five-second loop and no !!! Process exited line. Instead the container exits when your process does and Docker's unless-stopped restart policy brings it back, so a crash loop shows up as the container restarting rather than as a repeating log line.

The debugging advice below is the same either way — the exit code is still what you're after. You just read it from the event history rather than from a supervisor message.

What the exit code means

Code Usually means
0 Your process finished successfully. For a bot, that's still a bug — see below
1 Unhandled exception or an explicit error exit. Your stack trace is just above
137 Killed with SIGKILL — almost always out of memory
139 Segfault, usually in a native module
143 SIGTERM — a normal shutdown, e.g. you stopped or redeployed the app

Exit code 0 is the confusing one

A bot that exits 0 didn't crash — it finished. The loop restarts it anyway, so you get a perfectly clean restart cycle with no error to find.

The usual causes are all "your main function returned":

  • The login call was never awaited, so the script ran to the end while the connection was still opening.
  • The bot ran inside a script that had nothing keeping the event loop alive.
  • A library exited quietly on an auth failure — an invalid or revoked token often surfaces this way rather than as an exception.

If you see repeated code '0', don't hunt for a crash. Check that your entrypoint actually blocks, and check your token.

Exit code 137 means memory

137 is the container hitting its memory ceiling. Your RAM quota is a hard limit with no swap, so the kernel kills the process rather than slowing it down. Raise the quota, or find the leak — the classic one is accumulating objects per event and never releasing them.

This is the failure mode the restart loop disguises best: an OOM kill every few minutes looks like an app that's up.

Read the logs before they roll away

Container logs are capped at roughly 10 MB, kept as two 5 MB files. Under normal operation that's plenty of history.

In a crash loop it isn't. A bot restarting every five seconds that prints a stack trace and a few startup banners each time can churn through that budget quickly, and the original error scrolls out of retention — leaving you with thousands of identical restart lines and no first failure.

Practical consequences:

  • Look as soon as you notice. The oldest, most useful part of the log is what expires first.
  • Scroll up to the first occurrence, not the last. Later iterations often fail differently — a port still bound, a lock file left behind — and chasing the newest error sends you the wrong way.
  • Stop the app if you need time to think. Stopping ends the loop and freezes what's left of the log; a running crash loop keeps eating it.

A checklist that resolves most of them

  1. Read the exit code. It picks your branch — 0 means "finished", 137 means memory, 1 means read the trace above it.
  2. Find the first failure, not the most recent.
  3. Check secrets exist. A missing DISCORD_TOKEN or BOT_TOKEN shows up as an immediate exit, often before any of your own logging runs. Confirm the environment variables are actually set on the app.
  4. Check anything that must persist. If your bot opens a database at startup and the file vanished, it dies instantly on boot — data outside a volume doesn't survive a redeploy.
  5. Reproduce the start locally with the same env vars. A loop that restarts every five seconds is a slow way to test a one-line fix.
  6. Use the shell to look at the running container — check the file is where you think, the env var is set, the path exists. Shell access is included free on every app.

Make the next one easier to diagnose

  • Log at startup, before anything can fail. One line naming the version and the config it loaded turns "it exits instantly" into "it exits after config load".
  • Catch and log unhandled rejections, rather than letting the runtime exit silently.
  • Exit non-zero on fatal errors so the code tells you something. A bot that calls process.exit(0) on a bad token is indistinguishable from one that finished normally.
  • Don't add your own retry loop around the whole bot. The supervisor already restarts you; a second layer only hides the exit code and makes the log harder to read.
  • Stop the app while you debug. If your bot crashes after connecting to Discord, every restart spends one IDENTIFY — and enough of them will get your token reset.

FAQ

Why does my bot show as running when it keeps restarting?

On a zip deploy the process is supervised by a loop inside the container. When your bot exits, the loop restarts it after five seconds. The container never exits, so the status stays healthy and no restart counter increments.

How do I know if my bot is crash-looping?

Look in the logs for !!! Process exited with code repeating every five seconds. That line is printed by the supervisor each time your process dies. With your own image there's no such line — the container itself restarts instead, so check the event history.

What does exit code 137 mean?

The process was killed for exceeding the container's memory limit. Raise the RAM quota or fix the leak; there's no swap to absorb the overshoot.

My bot exits with code 0 and restarts — is that a crash?

No, it finished. Usually your main function returned because a login call wasn't awaited, nothing kept the event loop alive, or a library exited quietly on an invalid token.

Why can't I find the original error in the logs?

Logs are capped at about 10 MB. A fast crash loop overwrites the first failure with repeated restart output. Check early, and stop the app to freeze the log while you investigate.

Should I add my own restart logic?

No. The platform already restarts your process. A second retry loop hides the exit code, which is the most useful diagnostic you have.

Wrapping up

A supervised restart loop is the right default for a bot — a network blip at 3am fixes itself instead of taking you offline until morning. The cost is that "running" stops meaning "working", and you have to look one level deeper. That level is a single log line with a number in it, and once you know how to read it, most bot failures identify themselves in about thirty seconds.