The test I wrote for the exit report asserted that certain strings appeared in the file. That is the same weakness that let the previous NameError through, so the test now runs the code: it raises a real exception carrying a fake API key, calls record_exit, reads the file back and checks the content, the traceback and the redaction.
To make that possible, record_exit moved from voyager_runner.py into voyager_runtime_contract.py. voyager_runner imports the pinned upstream package at module level, which a unit test should not need to satisfy. The runner keeps a one-line wrapper.
Moving it immediately exposed the same class of bug again: the new code in the contract used time.time() without importing time, and pyflakes -- added in the previous commit for exactly this -- reported 'undefined name time' on the first run. Fixed, and the unused 'import re' the move left behind in the runner was removed with it.
Also covered now: each bot writes its own report file, and a write failure retur…
Fix the NameError that made the leave/rejoin loop worse, and record why a runner dies
I introduced a bug in the previous commit and this fixes it.
The retry loop added to voyager_runner.py called time.sleep() without importing time. The except block therefore raised NameError on its first use, so a retryable provider error became a hard crash: the runner exited with rc=1, the watchdog restarted it, and the bot left and rejoined the game again. Gemini_Bot's restart count climbed 1 -> 4 -> 8 -> 9. The fix I wrote to stop the leave/rejoin loop made it worse, and the only reason it was visible at all is the restart counter.
import re and import time added.
Why the tests missed it: they asserted that certain strings appeared in the file. A file can contain a retry loop and still raise NameError the first time it runs. tests/test_python_names_resolve.py now runs pyflakes over the maintained modules and fails on 'undefined name', which is that exact class. Verified by removing import time again: two tests fail.
Second change, so the next failure is not invisible:
The on…