DEV Community

John Wick
John Wick

Posted on

Protect Your Python Bots From Failing Over and Over Again

Stop Your Python Bots From Crashing: Complete Reliability Guide

Every bot crashes eventually — a malformed API response, a rate limit, a dependency that briefly goes down. The difference between a hobby project and something you can trust is what happens in the seconds after that crash. This guide covers python bot restart strategy end-to-end: how deployment failures actually happen, how to configure recovery properly with StayPresent, and how to avoid turning a single crash into a full crash loop.

Table of Contents

  1. Common Deployment Failures
  2. Understanding Crash Loops
  3. Configuring Auto Restart
  4. Restart Delays Explained
  5. Restart Limits and Counter Resets
  6. Health Monitoring
  7. Logging for Reliability
  8. Production Deployment Checklist
  9. Full Reliability Example
  10. Best Practices
  11. Common Mistakes
  12. FAQs
  13. Conclusion

Common Deployment Failures

Most Python bot outages in production trace back to one of a small set of causes:

  • An unhandled exception in an event handler
  • A third-party API returning an unexpected response shape
  • A dropped network connection during a long-running request
  • Running out of memory on a constrained free-tier container
  • A missing environment variable on a fresh deploy

None of these are exotic — they're the normal cost of running software against the real world. The question isn't whether they'll happen, it's whether your bot comes back on its own afterward.

Understanding Crash Loops

A crash loop happens when a bot restarts, hits the exact same failure immediately (a missing config value, a bad credential), crashes again, restarts again — indefinitely, often silently burning through hosting resources with no useful uptime to show for it.

This is why unlimited automatic restarts are dangerous on their own. Restart logic needs a ceiling.

crash -> restart -> crash -> restart -> crash -> restart -> ...
                (no ceiling = infinite loop)
Enter fullscreen mode Exit fullscreen mode

Configuring Auto Restart

StayPresent restarts your bot process automatically whenever it exits with a non-zero code:

staypresent.run(
    "bot.py",
    restart_on_crash=True,
)
Enter fullscreen mode Exit fullscreen mode

An exit code of 0 is always treated as an intentional, clean shutdown and never triggers a restart — useful if your bot has a legitimate "shut down now" command.

Restart Delays Explained

Restarting instantly after a crash can make things worse, particularly if the crash was caused by something transient like a rate limit — hammering the same failing request in a tight loop just makes the underlying problem worse. restart_delay adds a pause before each relaunch:

staypresent.run(
    "bot.py",
    restart_on_crash=True,
    restart_delay=2.0,
)
Enter fullscreen mode Exit fullscreen mode

For bots that talk to rate-limited APIs, a longer delay (5–10 seconds) is often safer than the default.

Restart Limits and Counter Resets

max_restarts puts a ceiling on consecutive crashes, which is exactly what prevents the infinite crash loop described above:

staypresent.run(
    "bot.py",
    restart_on_crash=True,
    max_restarts=5,
    restart_reset_after=60.0,
)
Enter fullscreen mode Exit fullscreen mode

The key detail is restart_reset_after: if the bot stays up successfully for that many seconds, the crash counter resets back to zero. This means a bot that crashes once a week doesn't slowly use up its restart budget over time — only a genuine, rapid crash loop hits the ceiling.

If max_restarts is exhausted, StayPresent exits the entire process using the bot's final exit code. This matters because it lets your hosting platform's own restart policy — Render, Railway, Docker, systemd — take over as a last resort, rather than StayPresent quietly exiting 0 as though nothing happened.

Health Monitoring

A dedicated /health endpoint, provisioned automatically by StayPresent, returns {"status": "ok"} independently of whatever you've configured at /:

staypresent.web.json({"status": "running", "version": "1.4.2"})
# /health still returns {"status": "ok"}
Enter fullscreen mode Exit fullscreen mode

Pointing your platform's health check — or an external uptime monitor — at /health gives you a stable signal that isn't affected by changes you make to your root route's response.

Logging for Reliability

Being able to see why a bot crashed matters as much as recovering from it. StayPresent logs under its own "staypresent" logger, so raising its verbosity during an incident doesn't pollute or restructure your bot's existing logs:

import logging
logging.getLogger("staypresent").setLevel(logging.INFO)
Enter fullscreen mode Exit fullscreen mode

Production Deployment Checklist

  • [ ] restart_on_crash=True with a sensible max_restarts
  • [ ] restart_delay tuned to the kind of failures you expect
  • [ ] restart_reset_after set so long-term stability doesn't get penalized
  • [ ] Platform health check pointed at /health
  • [ ] prod extra installed so Waitress serves in production
  • [ ] Logging level configured for the "staypresent" logger

Full Reliability Example

import os
import logging
import staypresent

logging.getLogger("staypresent").setLevel(logging.INFO)

staypresent.web.json({"status": "running"})

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
    restart_on_crash=True,
    max_restarts=5,
    restart_delay=3.0,
    restart_reset_after=90.0,
)
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Treat max_restarts as a safety net, not a substitute for fixing the underlying bug — a bot that crashes every few minutes needs a code fix, not a higher restart limit.
  • Set restart_delay higher for bots hitting rate-limited third-party APIs.
  • Use restart_reset_after values that roughly match how long your bot typically runs between deploys, so genuinely stable bots aren't treated as fragile.

Common Mistakes

  • Setting max_restarts very high "just in case." This turns a real crash loop into a slow, expensive one instead of catching it quickly.
  • Disabling restarts entirely in production (restart_on_crash=False) without a plan for what happens after — the process exits and stays down until someone notices.
  • Treating /health returning ok as proof the bot itself is fine. It only confirms the HTTP server is responsive; pair it with your own bot-side logging or metrics for full visibility.

FAQs

What happens after max_restarts is exhausted?
StayPresent exits the whole process with the bot's last exit code, allowing the hosting platform's own crash-restart policy to take over.

Does a SIGTERM from the platform count as a crash?
No — signals like SIGINT and SIGTERM trigger a clean, coordinated shutdown of both the server and the bot, not a restart.

Can I log restart events somewhere external?
StayPresent logs restarts through its own logger; you can attach a handler to logging.getLogger("staypresent") to forward those events wherever you want.

Conclusion

A python bot restart strategy isn't just "turn on auto-restart and hope." It's a combination of sensible delays, a real ceiling on consecutive failures, a counter that resets during genuine stability, and visibility into what's actually happening. StayPresent gives you all of that through a handful of parameters on staypresent.run(), so reliability stops being something you bolt on after your first outage.

pip install staypresent[prod]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)