fix(update): resume deferred Windows desktop updates

This commit is contained in:
fangliquanflq
2026-08-15 20:15:47 +08:00
committed by Teknium
parent 915ec169a0
commit fd24ac94e0
3 changed files with 78 additions and 4 deletions

View File

@@ -0,0 +1,15 @@
function Test-HermesUpdateShouldRetry {
param(
[int]$ExitCode,
[string]$InstallRoot
)
if ($ExitCode -eq 0) { return $false }
if ($ExitCode -ne 2) { return $true }
# Exit 2 is shared by non-retryable safety refusals and the self-lock
# deferral. Only the latter writes this marker, which a fresh Python
# process consumes before importing native modules.
$deferredInstallMarker = Join-Path $InstallRoot ".update-incomplete"
return Test-Path -LiteralPath $deferredInstallMarker
}

View File

@@ -1498,10 +1498,13 @@ try {
$res = Invoke-HermesStep $pythonExe $updateArgs "update"
Write-HandoffLog "hermes update exit code: $($res.Code)"
if ($res.Code -ne 0 -and $res.Code -ne 2) {
# One retry for the update-boundary class (fresh code on disk, stale
# code in memory). Exit 2 ("close all Hermes windows") is not retryable.
Write-HandoffLog "first attempt failed; retrying once (freshly pulled fix loads on the second run)"
. (Join-Path $PSScriptRoot "retry-policy.ps1")
if (Test-HermesUpdateShouldRetry -ExitCode $res.Code -InstallRoot $InstallRoot) {
# One retry for update-boundary failures. Most exit-2 safety refusals
# remain terminal, but self-lock deferral also uses exit 2 and writes
# .update-incomplete after the code swap. A fresh process consumes that
# marker before native imports, then resumes the full update pipeline.
Write-HandoffLog "first attempt left retryable update state; retrying once in a fresh process"
Publish-UiProgress "Retrying update"
$res = Invoke-HermesStep $pythonExe $updateArgs "update"
Write-HandoffLog "retry exit code: $($res.Code)"

View File

@@ -0,0 +1,56 @@
"""Windows Desktop handoff retry policy behavior."""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
RETRY_POLICY = REPO_ROOT / "scripts" / "desktop-update" / "retry-policy.ps1"
@pytest.mark.windows_only
def test_retry_policy_distinguishes_self_lock_deferral(tmp_path: Path) -> None:
install_root = tmp_path / "hermes-agent"
install_root.mkdir()
marker = install_root / ".update-incomplete"
policy = str(RETRY_POLICY).replace("'", "''")
root = str(install_root).replace("'", "''")
command = f"""
. '{policy}'
$withoutMarker = @(
(Test-HermesUpdateShouldRetry -ExitCode 0 -InstallRoot '{root}'),
(Test-HermesUpdateShouldRetry -ExitCode 1 -InstallRoot '{root}'),
(Test-HermesUpdateShouldRetry -ExitCode 2 -InstallRoot '{root}')
)
New-Item -ItemType File -Path (Join-Path '{root}' '.update-incomplete') | Out-Null
$withMarker = Test-HermesUpdateShouldRetry -ExitCode 2 -InstallRoot '{root}'
@{{ withoutMarker = $withoutMarker; withMarker = $withMarker }} |
ConvertTo-Json -Compress
"""
result = subprocess.run(
[
"powershell",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
command,
],
check=False,
capture_output=True,
text=True,
timeout=30,
)
assert result.returncode == 0, result.stdout + result.stderr
assert json.loads(result.stdout) == {
"withoutMarker": [False, True, False],
"withMarker": True,
}
assert marker.exists()