restart_or_exit spawned a replacement process and exited, with no attempt limit
and no backoff:
subprocess.Popen([sys.executable, os.path.abspath(sys.argv[0])])
os._exit(0)
On 2026-08-28 a 25-minute scheduled break left D2R on a screen the bot could
not re-enter:
=== BOT START ===
select_char: Could not find online/offline tabs
Restarting bot — game kept running
Because the failure was persistent, this span up a new process roughly every 20
seconds. Instances stacked (4 observed) and then refused taskkill.
The counter has to survive the exec — each restart is a NEW PROCESS, so an
in-memory counter cannot bound the chain. It lives in log/.restart_count,
is checked BEFORE spawning a replacement, and after 5 consecutive restarts the
bot stops with a Discord alert instead of looping, telling the user to return
D2R to the main menu.
A 5s-per-attempt backoff (capped at 60s) stops a fast failure spinning CPU or
stacking processes faster than they exit.
The count is cleared on reaching town, not at game end: the loop failed at
select_char, well before town, so reaching town is what proves recovery — and a
healthy bot never accumulates toward the cap.
Verified by falsification: restoring the unbounded restart makes the suite fail
with "restart loop is unbounded".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>