Adds Messenger.send_error across the Discord (red embed with screenshot attached) and generic (text-only) APIs. Bot._save_error_screenshot now also pushes the failure to the configured messenger after saving the screenshot to disk, so each approach/battle/exception failure is reviewable in Discord with the visual. Gated by new config discord_log_errors ([general], default 1) and the [discord_events] error toggle. Both optional/backward-compatible. Verified wiring and the suppression path via a stubbed messenger. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.9 KiB
Recovery & Error Logging
This document describes the failure-recovery and diagnostics features that let the bot survive broken runs and makes every failure reviewable after the fact.
1. Comprehensive failure logging
Every town action (src/town/a1.py … a5.py) and every run approach() method
(src/run/*.py) now logs the specific step that failed via Logger.error
instead of silently returning False. Examples of what now gets logged:
A1 resurrect: open_npc_menu(KASHYA) failed — NPC not found or menu did not openA4 open_wp: waypoint template A4_WP/A4_WP_2 not found — WP may be obscured or template mismatchVizier approach: use_wp('River of Flame') failedMephisto approach: go_to_act(3) failedgamble: go_to_act(4) failed from A1_TOWN_START — cannot reach Jamella
Town-manager fallbacks (resurrect, identify, open_stash, gamble, stash,
heal) also log when their go_to_act recovery hop fails, so you can tell whether
the problem was reaching the NPC, opening the menu, pressing the button, or the
panel never opening.
This means that "if Cain is missing, if the WP is not enabled, if some other game factor is not in place" the reason ends up in the log.
2. Error screenshots
When a run fails, the bot saves a screenshot so logs and visuals can be compared.
- Where:
log/screenshots/error/ - Filename:
error_<run_name>_<reason>_g<game#>_r<run#>_<YYYYMMDD_HHMMSS>.pnge.g.error_run_vizier_approach_failed_g12_r47_20260607_143501.png - When: captured on three failure types inside
Bot._run_wrapper:approach_failed— couldn't reach the bossbattle_failed— boss fight didn't completeexception_<ExceptionType>— an uncaught exception during battle
- The
g<game#>_r<run#>in the filename matches thegame/runfields in the structured event log (log/stats/events_*.jsonl) and theLoggeroutput, so a screenshot can be lined up with its log lines.
Discord delivery
When a run fails the bot also sends the error message and the screenshot to Discord (as a red embed with the screenshot attached). For the generic webhook API a text-only message is sent (the screenshot still lands on disk). This is on by default and can be turned off two ways:
[general] discord_log_errors=0— master switch for error → Discord[discord_events] error=0— fine-grained event toggle (Discord API only)
Config
config/params.ini → [general]:
; save a screenshot every time a run fails (falls back to info_screenshots if unset)
error_screenshots=1
; also send the error message + screenshot to Discord on each run failure
discord_log_errors=1
config/params.ini → [discord_events]:
; send run-failure notifications (message + error screenshot)
error=1
Set error_screenshots=0 to turn off error screenshots entirely. Old screenshots
are size-rotated using the info_* limits in [log_rotation] (or optional
error_max_files / error_max_mb if you add them).
3. Auto-disable broken runs (recovery)
Previously, repeated failures of one run could stall the whole bot. Now each run has its own consecutive failure counter:
- Every failed attempt of a run increments its counter; a single success resets it.
- When a run reaches the threshold of consecutive failures, it is disabled for the rest of the session. The bot keeps doing the other runs instead of stopping.
- Disabling is logged and sent to Discord (if enabled):
DISABLING run_vizier for this session: it failed 5 consecutive times. … - The disable is session-only / in-memory — it does not edit
params.ini. Restarting the bot re-enables the run. This is intentional so a transient problem (e.g. a game hiccup) doesn't permanently change your config. - If all runs end up disabled there are no routes left, so the bot saves a session report and stops cleanly (kills the game and exits) rather than looping over empty games or pointlessly restarting D2R.
Config
config/params.ini → [general]:
; after this many CONSECUTIVE failures of the same run, disable just that run
; for the rest of the session and keep doing the others. A success resets the count.
disable_run_after_failures=5
Set this high (e.g. 9999) to effectively disable the auto-disable behaviour.
Implementation notes
src/bot.py__init__:_run_failure_counts,_disabled_runs,_max_run_failures_record_run_result(run_name, failed): counts failures, disables runs, resets on success_save_error_screenshot(run_name, reason): writes the diagnostic screenshot_run_wrapper(...): calls both on each failure path (approach / battle / exception)
src/config.py: parseserror_screenshotsanddisable_run_after_failures(both via_select_optional, so existing configs keep working).src/utils/log_rotation.py: routes theerror/directory through rotation.