The database migration recorded steps as done even when they had failed, so a
resume skipped them and the loop kept going on a broken database.
execute.py
- exec_command_live() left exit_code = None when an exception was raised. None
is falsy, so « if not status: » marked the step done and
« if status and wait_at_error » skipped the error prompt: a crashed command
was reported as a success. Both except blocks now set exit_code = 1.
- The « no Odoo version installed » path returned a bare -1 while callers
unpack a tuple (status, cmd), raising ValueError instead of surfacing the
failure. It now returns the same shape the caller asked for.
todo_upgrade.py
- todo_upgrade_execute(): treat a None status as a failure (defence in depth).
- Database migration (OpenUpgrade): the return code was discarded, with an
explicit « TODO detect error », and the state was written unconditionally.
It is now captured; on failure the loop stops instead of migrating the next
version on top of a half-migrated database.
- Neutralization: the flag was set before the « if not status » test, making
that test dead code. Removed the unconditional assignment.
- Clone and fix-migration hook: their return codes were never captured, so the
steps were marked done whatever happened. Both are now checked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>