diff --git a/.agents/skills/multi-agent-mux-update/SKILL.md b/.agents/skills/multi-agent-mux-update/SKILL.md index c322b63..a1eaff2 100644 --- a/.agents/skills/multi-agent-mux-update/SKILL.md +++ b/.agents/skills/multi-agent-mux-update/SKILL.md @@ -27,4 +27,4 @@ bash .agents/skills/multi-agent-mux-update/scripts/update.sh --yes | Monitor | stopped first, started again afterwards only if it ran (`MAM_NO_SUBSCRIBER_DAEMON=1`: not started). | | Install | the installer at `MAM_INSTALLER_URL` (default: Gitea `main`) runs in place; `.mam` is kept. On failure `.agents/`, `.mam_deploy/`, root files, `.venv`, the installer's `.mam` files and the registry are restored from `.mam/update-backup/`. | | Registry | converted by the new code (`lib_py.registry_format`); a failed conversion keeps the install and exits nonzero. | -| Exit | `0` updated, `1` refused or failed, `2` bad arguments. | +| Exit | `0` updated (or cancelled at the prompt), `1` refused or failed, `2` bad arguments; a failed registry conversion after a good install exits with the converter's nonzero code. | diff --git a/.agents/skills/multi-agent-mux-update/scripts/install_phase.sh b/.agents/skills/multi-agent-mux-update/scripts/install_phase.sh index 5741ffb..78a6561 100755 --- a/.agents/skills/multi-agent-mux-update/scripts/install_phase.sh +++ b/.agents/skills/multi-agent-mux-update/scripts/install_phase.sh @@ -81,7 +81,14 @@ MON="${3:-0}" TMPD="${4:-}" trap '[ -z "$TMPD" ] || rm -rf "$TMPD"' EXIT -set_state installing || { echo "ERROR: cannot write $BK/state" >&2; release_marker; exit 1; } +if ! set_state installing; then + # Nothing was installed yet: drop the backup and put the monitor back as phase 1 found it. + echo "ERROR: cannot write $BK/state; nothing was changed" >&2 + rm -rf "$BK" + restart_old_monitor "$MON" + release_marker + exit 1 +fi URL="${MAM_INSTALLER_URL:-https://git.godopu.com/tmpl/multi-agent-mux/raw/branch/main/deploy/install.sh}" echo "install: $URL" irc=0 diff --git a/tests/test_issue72_update.py b/tests/test_issue72_update.py index 2d3284f..552ed83 100644 --- a/tests/test_issue72_update.py +++ b/tests/test_issue72_update.py @@ -702,6 +702,26 @@ def test_leftover_whose_restore_fails_is_kept_and_nothing_more_changes(box): assert not (box.mam / "update-in-progress").exists() +def test_a_state_write_failure_before_the_install_changes_nothing_and_restarts_the_monitor(box): + real_mv = shutil.which("mv") + fake = box.bin / "mv" + fake.write_text("#!/usr/bin/env bash\n" + "src=\"${@: -2:1}\"\n" + "case \"$src\" in */update-backup/state.tmp) grep -qx installing \"$src\" && exit 1 ;; esac\n" + "exec %s \"$@\"\n" % real_mv) + fake.chmod(0o755) + box.stub_monitor() + box.start_monitor() + box.installer("touch \"$ws/INSTALLER-RAN\"\n") + res = box.update() + assert res.returncode == 1, res.stdout + res.stderr + assert not (box.ws / "INSTALLER-RAN").exists() + assert box.monitor_starts() == 2, "the monitor it stopped is started again" + _until(lambda: bool(box.monitor_pids())) + assert not (box.mam / "update-backup").exists() and not (box.mam / "update-in-progress").exists() + assert "nothing was changed" in res.stderr + + def test_the_backup_lives_in_the_workspace_and_is_gone_after_success(box): box.installer("test -f \"$ws/.mam/update-backup/state\" && grep -qx installing \"$ws/.mam/update-backup/state\" " "&& test -f \"$ws/.mam/update-backup/files/.agents/skills/lib.sh\" && touch \"$ws/../saw-backup\"\n")