From 454b77215f2a0eee683ebf6822dbe89fb5188e11 Mon Sep 17 00:00:00 2001 From: Godopu Date: Tue, 27 Oct 2026 14:35:35 +0000 Subject: [PATCH] fix(update): a state write failure before the install restarts the monitor; the exit table names the conversion code (Issue #72, review notes) install_phase.sh exited when it could not mark the backup `installing`, before the installer ran, without starting the monitor phase 1 had stopped (decision 2) and leaving the backup behind. Nothing was changed at that point, so it now removes the backup, restarts the monitor if it ran, releases the marker and exits 1. SKILL.md's exit row said 1 for every failure; a failed registry conversion after a good install passes the converter's nonzero code through, and a cancelled prompt exits 0. Co-Authored-By: Claude Opus 5.5 --- .../skills/multi-agent-mux-update/SKILL.md | 2 +- .../scripts/install_phase.sh | 9 ++++++++- tests/test_issue72_update.py | 20 +++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) 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")