Extract installed_target_matches_request helper for update skip check and use describe field for exact version matching

Add installed_target_matches_request helper to check if requested update target matches current installation. Replace short_tag comparison with describe field to ensure exact commit match - prevents skipping updates when current version is ahead of requested tag (e.g. v1.11-12-ge9d9c93d vs v1.11). Return false for "latest" selector tags to force update check. Add test coverage
This commit is contained in:
frdel 2026-03-26 13:16:02 +01:00
parent e9d9c93d13
commit 0bca80a49f
3 changed files with 89 additions and 12 deletions

View file

@ -1055,6 +1055,23 @@ def queue_update_request(
return payload
def installed_target_matches_request(
current_info: dict[str, str],
*,
requested_branch: str,
requested_tag: str,
) -> bool:
normalized_tag = requested_tag.strip()
if not normalized_tag or is_latest_selector_tag(normalized_tag):
return False
current_branch = current_info.get("branch", "").strip()
if requested_branch.strip() and current_branch != requested_branch.strip():
return False
return current_info.get("describe", "").strip() == normalized_tag
def trigger_update_command(args: list[str]) -> int:
parser = argparse.ArgumentParser(
prog="trigger_self_update.sh",
@ -1137,11 +1154,10 @@ def docker_run_ui() -> int:
current = get_repo_version_info(REPO_DIR)
requested_branch = str(request_data.get("branch", "")).strip()
requested_tag = str(request_data.get("tag", "")).strip()
current_branch = current.get("branch", "").strip()
if (
requested_tag
and current["short_tag"] == requested_tag
and (not requested_branch or current_branch == requested_branch)
if installed_target_matches_request(
current,
requested_branch=requested_branch,
requested_tag=requested_tag,
):
logger.log(
"Requested tag already matches the installed version, skipping file replacement."

View file

@ -393,7 +393,9 @@ def test_self_update_frontend_uses_preloaded_select():
assert "getLastStatusBadgeClass(status)" in content
assert "this.info?.current?.display_version" in content
assert "resetRestartState()" in content
assert "restartRequestError" in content
assert "restartRequestStarted" in content
assert "restartResponse.status >= 500" in content
assert "while Agent Zero was shutting down" in content
assert "await notificationStore.frontendWarning(" not in content
assert "status-pill-error" in content
assert "status-pill-success" in content
@ -658,6 +660,47 @@ def test_self_update_manager_explicit_tag_uses_peeled_commit(monkeypatch):
assert resolved["expected_commit"] == "192d6e2cae1a85c0a2e7a6ecf41c153b39f1b4c6"
def test_self_update_manager_skip_check_requires_exact_describe_match():
manager = load_self_update_manager()
assert (
manager.installed_target_matches_request(
{
"branch": "development",
"describe": "v1.11",
"short_tag": "v1.11",
},
requested_branch="development",
requested_tag="v1.11",
)
is True
)
assert (
manager.installed_target_matches_request(
{
"branch": "development",
"describe": "v1.11-12-ge9d9c93d",
"short_tag": "v1.11",
},
requested_branch="development",
requested_tag="v1.11",
)
is False
)
assert (
manager.installed_target_matches_request(
{
"branch": "development",
"describe": "v1.11",
"short_tag": "v1.11",
},
requested_branch="development",
requested_tag="latest",
)
is False
)
def test_self_update_manager_fetch_release_refs_checks_peeled_tag_commit(monkeypatch):
manager = load_self_update_manager()
commands = []

View file

@ -497,9 +497,10 @@ const model = {
);
this.ensureProgressOverlay();
let restartRequestError = null;
let restartRequestStarted = false;
try {
const token = await API.getCsrfToken();
restartRequestStarted = true;
const restartResponse = await fetch("/api/restart", {
method: "POST",
credentials: "same-origin",
@ -511,19 +512,36 @@ const model = {
body: JSON.stringify({}),
});
if (restartResponse && !restartResponse.ok) {
restartRequestError = new Error(
`Restart request failed with HTTP ${restartResponse.status}.`
if (restartResponse.status >= 500) {
console.warn(
`Restart request returned HTTP ${restartResponse.status} while Agent Zero was shutting down. Continuing to wait for the new runtime.`
);
this.setRestartState(
"Restarting backend",
"Agent Zero is shutting down and applying the update. Waiting for the new runtime to come back healthy."
);
} else {
throw new Error(
`Restart request failed with HTTP ${restartResponse.status}.`
);
}
} else {
this.setRestartState(
"Restarting backend",
"Agent Zero accepted the restart request. Waiting for the updater to take over."
);
throw restartRequestError;
}
} catch (error) {
if (restartRequestError && error === restartRequestError) {
if (!restartRequestStarted) {
this.restarting = false;
this.resetRestartState();
this.removeProgressOverlay();
throw error;
}
// The restart request often terminates the backend mid-flight.
console.warn(
"Restart request connection closed while Agent Zero was restarting:",
error
);
}
const maxWaitMs =