{"repo":"NousResearch/hermes-agent","public_id":"26670","display_label":"Windows update blocked by locked venv files and processes","summary":"Nearly all members concern Windows updates, installs, or venv recreation encountering locked files (such as hermes.exe, .pyd files, or pythonw.exe) held by running Hermes-related processes like the gateway. The group includes both bug reports and proposed changes that stop, pause, or work around these processes; one item concerns locking during a desktop packaging step.","subsystem":"apps/bootstrap-installer","counts":{"members":30,"open_issues":0,"open_issues_distinct":0,"open_prs":3,"closed_issues":16,"merged_prs":3,"attempt_prs":11},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":52044,"merged_at":"2026-06-24T19:16:58+00:00"},"resolution":{"state":"partially_resolved","by":"pr-26677,pr-42100,pr-52044"},"freshness":{"state":"verified","last_verified_at":"2026-06-24T19:16:58+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.06491954022988503},"fix_edges":[{"issue_number":23327,"pr_number":26677,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-05-19T18:10:52+00:00"},{"issue_number":26670,"pr_number":26677,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-19T18:10:52+00:00"},{"issue_number":41178,"pr_number":42100,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-08T18:51:32+00:00"},{"issue_number":47036,"pr_number":52044,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-24T19:16:58+00:00"},{"issue_number":47557,"pr_number":52044,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-24T19:16:58+00:00"},{"issue_number":47910,"pr_number":52044,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-24T19:16:58+00:00"}],"members":[{"kind":"issue","number":26670,"title":"fix(update): hermes update on Windows fails to quarantine hermes.exe when another instance is running","url":null,"state":"closed"},{"kind":"issue","number":37731,"title":"Windows installer fails with \"Access denied\" — wipes venv while app + leaked child processes still hold files open","url":null,"state":"closed"},{"kind":"issue","number":38789,"title":"[Bug]: Windows hermes update fails when Gateway/Dashboard lock native .pyd dependencies","url":null,"state":"closed"},{"kind":"issue","number":39431,"title":"[Bug]: Auto-update fails on Windows — speedups.pyd access denied (venv bootstrap)","url":null,"state":"closed"},{"kind":"issue","number":41178,"title":"[Bug]: Windows Desktop self-update rebuild fails because running Hermes.exe locks release/win-unpacked files","url":null,"state":"closed"},{"kind":"issue","number":43268,"title":"[Bug] Windows Desktop auto-update fails every time: venv locked by running processes","url":null,"state":"closed"},{"kind":"issue","number":47036,"title":"Bootstrap fails to recreate venv when gateway process is running — hermes.exe locked on Windows","url":null,"state":"closed"},{"kind":"issue","number":47557,"title":"[Bug] Windows: scripts/install.ps1 venv recreation fails when pythonw.exe holds .pyd locks — only kills hermes.exe","url":null,"state":"closed"},{"kind":"issue","number":47910,"title":"Windows installer fails to recreate venv: pre-delete kill only targets hermes.exe, not the pythonw.exe gateway holding the lock","url":null,"state":"closed"},{"kind":"issue","number":48100,"title":"[Bug]: Windows installer fails with \"Access Denied\" on pythonw.exe during auto-update","url":null,"state":"closed"},{"kind":"issue","number":49428,"title":"Windows self-replacement deadlock causes hermes update to fail (Error 5 / Access Denied) — suggest detached updater","url":null,"state":"closed"},{"kind":"issue","number":51015,"title":"BUG: Windows pip upgrade fails with os error 32 (hermes.exe locked by self)","url":null,"state":"closed"},{"kind":"issue","number":58387,"title":"Windows update fails: locked .pyd files by running python/pythonw processes","url":null,"state":"closed"},{"kind":"issue","number":58730,"title":"Windows: hermes update fails persistently due to hermes.exe file lock (cannot overwrite running executable)","url":null,"state":"closed"},{"kind":"issue","number":63300,"title":"hermes update fails on Windows when headroom proxy/tui_gateway processes are running","url":null,"state":"closed"},{"kind":"issue","number":68760,"title":"hermes.exe locked during `hermes update` on Windows (WinError 32)","url":null,"state":"closed"},{"kind":"pr","number":26677,"title":"fix(update): quarantine hermes.exe vs concurrent Windows instance (#26670)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/main.py","pyproject.toml","tests/hermes_cli/conftest.py","tests/hermes_cli/test_update_concurrent_quarantine.py","website/docs/getting-started/updating.md"],"fix_hunks":[{"file":"hermes_cli/main.py","hunks":["@@ -6954,7 +6954,95 @@ def _hermes_exe_shims(scripts_dir: Path) -> list[Path]:\n     ]\n \n \n-def _quarantine_running_hermes_exe(scripts_dir: Path) -> list[tuple[Path, Path]]:\n+def _detect_concurrent_hermes_instances(\n+    scripts_dir: Path, *, exclude_pid: int | None = None\n+) -> list[tuple[int, str]]:\n+    \"\"\"Find other live processes whose .exe is one of our entry-point shims.\n+\n+    Windows blocks DELETE/REPLACE on a running .exe — and even RENAME on the\n+    same .exe when another process opened it without ``FILE_SHARE_DELETE``.\n+    The Hermes Desktop Electron app spawns ``hermes.EXE`` as a backend child,\n+    so during ``hermes update`` the user-invoked process and the desktop's\n+    child both hold the same file. The quarantine rename then fails with\n+    ``[WinError 32]`` and uv inherits the lock.\n+\n+    This helper enumerates processes whose ``exe`` matches one of the venv's\n+    shims (``hermes.exe`` / ``hermes-gateway.exe``) and returns ``(pid,\n+    process_name)`` pairs. The caller's own PID is excluded so the running\n+    ``hermes update`` invocation never reports itself.\n+\n+    Returns an empty list off-Windows, on missing psutil, or when no other\n+    instances exist. Never raises — process enumeration is best-effort.\n+    \"\"\"\n+    if not _is_windows():\n+        return []\n+\n+    try:\n+        import psutil\n+    except Exception:\n+        return []\n+\n+    if exclude_pid is None:\n+        exclude_pid = os.getpid()\n+\n+    # Resolve every shim path to its canonical form once for cheap comparison.\n+    shim_paths: set[str] = set()\n+    for shim in _hermes_exe_shims(scripts_dir):\n+        try:\n+            shim_paths.add(str(shim.resolve()).lower())\n+        except OSError:\n+            shim_paths.add(str(shim).lower())\n+    if not shim_paths:\n+        return []\n+\n+    matches: list[tuple[int, str]] = []\n+    try:\n+        proc_iter = psutil.process_iter([\"pid\", \"exe\", \"name\"])\n+    except Exception:\n+        return []\n+\n+    for proc in proc_iter:\n+        try:\n+            info = proc.info\n+        except Exception:\n+            continue\n+        pid = info.get(\"pid\")\n+        exe = info.get(\"exe\")\n+        if not exe or pid is None or pid == exclude_pid:\n+            continue\n+        try:\n+            exe_norm = str(Path(exe).resolve()).lower()\n+        except (OSError, ValueError):\n+            exe_norm = str(exe).lower()\n+        if exe_norm in shim_paths:\n+            name = info.get(\"name\") or Path(exe).name\n+            matches.append((int(pid), str(name)))\n+\n+    return matches\n+\n+\n+def _format_concurrent_instances_message(\n+    matches: list[tuple[int, str]], scripts_dir: Path\n+) -> str:\n+    \"\"\"Build a human-readable explanation + remediation hint for the user.\"\"\"\n+    shim = scripts_dir / \"hermes.exe\"\n+    lines = [\"✗ Another hermes.exe is running:\"]\n+    for pid, name in matches:\n+        lines.append(f\"    PID {pid}  {name}\")\n+    lines.append(\"\")\n+    lines.append(f\"  Updating now would fail to overwrite {shim} because\")\n+    lines.append(\"  Windows blocks REPLACE on a running executable.\")\n+    lines.append(\"\")\n+    lines.append(\"  Close Hermes Desktop, exit any open `hermes` REPLs, and\")\n+    lines.append(\"  stop the gateway (`hermes gateway stop`) before retrying.\")\n+    lines.append(\"  Override with `hermes update --force` if you've already\")\n+    lines.append(\"  confirmed those processes will not write to the venv.\")\n+    return \"\\n\".join(lines)\n+\n+\n+def _quarantine_running_hermes_exe(\n+    scripts_dir: Path, *, max_attempts: int = 4\n+) -> list[tuple[Path, Path]]:\n     \"\"\"Pre-empt Windows file lock on the running ``hermes.exe``.\n \n     Windows allows RENAMING a mapped/running executable (the kernel tracks the","@@ -6967,29 +7055,129 @@ def _quarantine_running_hermes_exe(scripts_dir: Path) -> list[tuple[Path, Path]]\n     fresh shims at the original paths. The ``.old`` files are cleaned up on\n     the next hermes invocation by ``_cleanup_quarantined_exes``.\n \n+    Rename can still fail when *another* process has opened the .exe without\n+    ``FILE_SHARE_DELETE`` — typically AV real-time scanners with transient\n+    handles (recovers in <1s), or the Hermes Desktop backend child process\n+    (won't recover until the user closes it). We mitigate:\n+\n+    1. Retry up to ``max_attempts`` times with exponential backoff\n+       (100/250/500/1000 ms). Handles the AV-scanner case.\n+    2. If all retries fail, schedule the .exe for replacement on next\n+       reboot via ``MoveFileExW(MOVEFILE_DELAY_UNTIL_REBOOT)``. This still\n+       lets uv create a fresh shim at the original path (Windows will keep\n+       the old file's content under a new name until the reboot), so the\n+       update can complete; the user just needs to reboot to fully unload\n+       the stale image.\n+    3. Print a clear warning naming the most likely culprit (running\n+       Hermes Desktop / gateway / REPL) and pointing to ``--force``.\n+\n     Returns the list of (original, quarantined) pairs so the caller can roll\n-    back if the install itself fails before uv writes a replacement.\n+    back if the install itself fails before uv writes a replacement. Pairs\n+    where we used ``MOVEFILE_DELAY_UNTIL_REBOOT`` are NOT returned — they\n+    are already deferred and roll-back is meaningless.\n     \"\"\"\n     moved: list[tuple[Path, Path]] = []\n     if not _is_windows():\n         return moved\n \n     import time\n+\n     stamp = int(time.time() * 1000)\n+    # Backoff schedule: first attempt is immediate, subsequent ones sleep.\n+    # 100ms / 250ms / 500ms covers the typical AV scanner re-scan window.\n+    backoff_ms = [0, 100, 250, 500, 1000]\n+    attempts = max(1, min(max_attempts, len(backoff_ms)))\n+\n     for shim in _hermes_exe_shims(scripts_dir):\n         if not shim.exists():\n             continue\n         target = shim.with_suffix(shim.suffix + f\".old.{stamp}\")\n-        try:\n-            shim.rename(target)\n-            moved.append((shim, target))\n-        except OSError as e:\n-            # Best-effort: keep going. uv's failure later will surface the\n-            # real error; this is a heuristic, not a hard guarantee.\n-            print(f\"  ⚠ Could not quarantine {shim.name}: {e}\")\n+\n+        last_exc: OSError | None = None\n+        for attempt in range(attempts):\n+            delay = backoff_ms[attempt] / 1000.0\n+            if delay:\n+                time.sleep(delay)\n+            try:\n+                shim.rename(target)\n+                moved.append((shim, target))\n+                last_exc = None\n+                break\n+            except OSError as e:\n+                last_exc = e\n+                continue\n+\n+        if last_exc is None:\n+            continue\n+\n+        # All in-process renames failed. Try MoveFileEx with\n+        # MOVEFILE_DELAY_UNTIL_REBOOT as a last resort. This succeeds in the\n+        # exact case where the inline rename failed (another process holds\n+        # the handle without share-delete), at the cost of requiring a\n+        # reboot to fully reclaim the old .exe.\n+        scheduled = _schedule_replace_on_reboot(shim, target)\n+        if scheduled:\n+            print(\n+                f\"  ⚠ {shim.name} is locked by another process; scheduled \"\n+                f\"replacement on next reboot.\"\n+            )\n+            print(\n+                \"    The new shim was written at the same path, but a \"\n+                \"reboot is needed to fully unload the old one.\"\n+            )\n+            # Do NOT append to ``moved``: we don't want roll-back to undo a\n+            # reboot-deferred operation.\n+            continue\n+\n+        # Truly couldn't budge the .exe. Print an actionable warning and let\n+        # uv try its luck — sometimes uv's own retry handling pulls through.\n+        print(\n+            f\"  ⚠ Could not quarantine {shim.name} ({last_exc.__class__.__name__}: \"\n+            f\"another process is holding it open).\"\n+        )\n+        print(\n+            \"    Close Hermes Desktop, exit other `hermes` REPLs, stop the \"\n+            \"gateway, or pause AV scanning, then re-run `hermes update`.\"\n+        )\n+\n     return moved\n \n \n+def _schedule_replace_on_reboot(shim: Path, quarantine_target: Path) -> bool:\n+    \"\"\"Schedule ``shim`` -> ``quarantine_target`` via PendingFileRenameOperations.\n+\n+    Uses Win32 ``MoveFileExW`` with ``MOVEFILE_REPLACE_EXISTING |\n+    MOVEFILE_DELAY_UNTIL_REBOOT``. The OS persists the rename in\n+    ``HKLM\\\\System\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\\n+    PendingFileRenameOperations`` and applies it before any user-mode code\n+    runs on next boot — at which point no process can hold the .exe.\n+\n+    Returns ``True`` if the schedule call succeeded, ``False`` otherwise\n+    (non-Windows, ctypes failure, lack of privilege, etc.). Never raises.\n+    \"\"\"\n+    if not _is_windows():\n+        return False\n+    try:\n+        import ctypes\n+        from ctypes import wintypes\n+\n+        MOVEFILE_REPLACE_EXISTING = 0x1\n+        MOVEFILE_DELAY_UNTIL_REBOOT = 0x4\n+\n+        MoveFileExW = ctypes.windll.kernel32.MoveFileExW\n+        MoveFileExW.argtypes = [wintypes.LPCWSTR, wintypes.LPCWSTR, wintypes.DWORD]\n+        MoveFileExW.restype = wintypes.BOOL\n+\n+        ok = MoveFileExW(\n+            str(shim),\n+            str(quarantine_target),\n+            MOVEFILE_REPLACE_EXISTING | MOVEFILE_DELAY_UNTIL_REBOOT,\n+        )\n+        return bool(ok)\n+    except Exception:\n+        return False\n+\n+\n def _restore_quarantined_exes(moved: list[tuple[Path, Path]]) -> None:\n     \"\"\"Roll back ``_quarantine_running_hermes_exe`` if uv didn't write replacements.\"\"\"\n     for original, quarantined in moved:","@@ -7775,6 +7963,18 @@ def _cmd_update_impl(args, gateway_mode: bool):\n     print(\"⚕ Updating Hermes Agent...\")\n     print()\n \n+    # On Windows, abort early if another hermes.exe is holding the venv shim\n+    # open. Continuing would result in a string of WinError 32 warnings and\n+    # then either a deferred-rename leftover or a failed git-pull fast path\n+    # that silently falls back to the slower ZIP route. See issue #26670.\n+    if _is_windows() and not getattr(args, \"force\", False):\n+        scripts_dir = _venv_scripts_dir()\n+        if scripts_dir is not None:\n+            concurrent = _detect_concurrent_hermes_instances(scripts_dir)\n+            if concurrent:\n+                print(_format_concurrent_instances_message(concurrent, scripts_dir))\n+                sys.exit(2)\n+\n     # Pre-update backup — runs before any git/file mutation so users can\n     # always roll back to the exact state they had before this update.\n     _run_pre_update_backup(args)","@@ -11895,6 +12095,12 @@ def cmd_claw(args):\n         default=False,\n         help=\"Assume yes for interactive prompts (config migration, stash restore). API-key entry is skipped; run 'hermes config migrate' separately for those.\",\n     )\n+    update_parser.add_argument(\n+        \"--force\",\n+        action=\"store_true\",\n+        default=False,\n+        help=\"Windows: proceed with the update even when another hermes.exe is detected. The concurrent process will likely cause WinError 32 warnings and may leave a reboot-deferred .exe replacement.\",\n+    )\n     update_parser.set_defaults(func=cmd_update)\n \n     # ========================================================================="]},{"file":"pyproject.toml","hunks":["@@ -220,6 +220,7 @@ include = [\"agent\", \"agent.*\", \"tools\", \"tools.*\", \"hermes_cli\", \"gateway\", \"gat\n testpaths = [\"tests\"]\n markers = [\n     \"integration: marks tests requiring external services (API keys, Modal, etc.)\",\n+    \"real_concurrent_gate: opt out of the autouse stub that disables _detect_concurrent_hermes_instances\",\n ]\n addopts = \"-m 'not integration' -n auto\"\n "]},{"file":"tests/hermes_cli/conftest.py","hunks":["@@ -17,3 +17,30 @@ def all_assignees_spawnable(monkeypatch):\n     \"\"\"\n     from hermes_cli import profiles\n     monkeypatch.setattr(profiles, \"profile_exists\", lambda name: True)\n+\n+\n[redacted:email](autouse=True)\n+def _suppress_concurrent_hermes_gate(request, monkeypatch):\n+    \"\"\"Default ``_detect_concurrent_hermes_instances`` to ``[]`` for every test.\n+\n+    The Windows update path now refuses to proceed when another\n+    ``hermes.exe`` is detected (issue #26670). On a developer's Windows\n+    machine running the test suite via ``hermes`` itself, this would\n+    flag the running agent as a concurrent instance and abort every\n+    ``cmd_update`` test. Tests that want to exercise the gate explicitly\n+    re-patch ``_detect_concurrent_hermes_instances`` with their own\n+    return value — autouse here gives a clean default without touching\n+    the rest of the suite.\n+\n+    Tests that need to call the REAL function (e.g. unit tests for the\n+    helper itself) opt out with ``@pytest.mark.real_concurrent_gate``.\n+    \"\"\"\n+    if request.node.get_closest_marker(\"real_concurrent_gate\"):\n+        return\n+    try:\n+        from hermes_cli import main as _cli_main\n+    except Exception:\n+        return\n+    monkeypatch.setattr(\n+        _cli_main, \"_detect_concurrent_hermes_instances\", lambda *_a, **_k: []\n+    )"]},{"file":"tests/hermes_cli/test_update_concurrent_quarantine.py","hunks":["@@ -0,0 +1,328 @@\n+\"\"\"Tests for issue #26670 — concurrent hermes.exe detection and improved\n+quarantine retry / reboot-deferred fallback during `hermes update` on Windows.\n+\n+These tests force ``_is_windows`` to return ``True`` via patching so the\n+Windows-specific code paths can be exercised on any host.\n+\"\"\"\n+\n+from __future__ import annotations\n+\n+import os\n+import sys\n+import types\n+from pathlib import Path\n+from types import SimpleNamespace\n+from unittest.mock import MagicMock, patch\n+\n+import pytest\n+\n+from hermes_cli import main as cli_main\n+\n+\n+# Tests in this module either exercise the REAL _detect_concurrent_hermes_instances\n+# helper (and need the autouse stub in tests/hermes_cli/conftest.py disabled),\n+# or supply their own explicit return value via patch.object. Mark the whole\n+# module so the conftest fixture skips its default stub.\n+pytestmark = pytest.mark.real_concurrent_gate\n+\n+\n+# ---------------------------------------------------------------------------\n+# _detect_concurrent_hermes_instances\n+# ---------------------------------------------------------------------------\n+\n+\n+def _make_proc(pid: int, exe: str, name: str = \"hermes.exe\"):\n+    \"\"\"Build a duck-typed psutil Process stand-in with the .info dict.\"\"\"\n+    proc = MagicMock()\n+    proc.info = {\"pid\": pid, \"exe\": exe, \"name\": name}\n+    return proc\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_detect_concurrent_returns_empty_when_no_other_processes(_winp, tmp_path):\n+    scripts_dir = tmp_path\n+    (scripts_dir / \"hermes.exe\").write_bytes(b\"\")\n+    (scripts_dir / \"hermes-gateway.exe\").write_bytes(b\"\")\n+\n+    fake_psutil = types.SimpleNamespace(process_iter=lambda attrs: iter([]))\n+    with patch.dict(sys.modules, {\"psutil\": fake_psutil}):\n+        result = cli_main._detect_concurrent_hermes_instances(scripts_dir)\n+\n+    assert result == []\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_detect_concurrent_excludes_self_pid(_winp, tmp_path):\n+    scripts_dir = tmp_path\n+    shim = scripts_dir / \"hermes.exe\"\n+    shim.write_bytes(b\"\")\n+    my_pid = os.getpid()\n+\n+    procs = [_make_proc(my_pid, str(shim), \"hermes.exe\")]\n+    fake_psutil = types.SimpleNamespace(process_iter=lambda attrs: iter(procs))\n+    with patch.dict(sys.modules, {\"psutil\": fake_psutil}):\n+        result = cli_main._detect_concurrent_hermes_instances(scripts_dir)\n+\n+    assert result == []\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_detect_concurrent_finds_other_hermes_process(_winp, tmp_path):\n+    scripts_dir = tmp_path\n+    shim = scripts_dir / \"hermes.exe\"\n+    shim.write_bytes(b\"\")\n+\n+    other_pid = os.getpid() + 1\n+    procs = [\n+        _make_proc(other_pid, str(shim), \"hermes.exe\"),\n+        _make_proc(os.getpid() + 2, r\"C:\\\\Windows\\\\System32\\\\notepad.exe\", \"notepad.exe\"),\n+    ]\n+    fake_psutil = types.SimpleNamespace(process_iter=lambda attrs: iter(procs))\n+    with patch.dict(sys.modules, {\"psutil\": fake_psutil}):\n+        result = cli_main._detect_concurrent_hermes_instances(scripts_dir)\n+\n+    assert result == [(other_pid, \"hermes.exe\")]\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_detect_concurrent_matches_case_insensitively(_winp, tmp_path):\n+    scripts_dir = tmp_path\n+    shim = scripts_dir / \"hermes.exe\"\n+    shim.write_bytes(b\"\")\n+\n+    # Simulate the desktop spawning hermes.EXE (uppercase ext) from same path\n+    upper = str(shim).replace(\"hermes.exe\", \"HERMES.EXE\")\n+    procs = [_make_proc(9999, upper, \"HERMES.EXE\")]\n+    fake_psutil = types.SimpleNamespace(process_iter=lambda attrs: iter(procs))\n+    with patch.dict(sys.modules, {\"psutil\": fake_psutil}):\n+        result = cli_main._detect_concurrent_hermes_instances(scripts_dir)\n+\n+    assert result == [(9999, \"HERMES.EXE\")]\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_detect_concurrent_no_psutil_returns_empty(_winp, tmp_path):\n+    scripts_dir = tmp_path\n+    (scripts_dir / \"hermes.exe\").write_bytes(b\"\")\n+\n+    # Block psutil import — simulate environment without it.\n+    with patch.dict(sys.modules, {\"psutil\": None}):\n+        result = cli_main._detect_concurrent_hermes_instances(scripts_dir)\n+\n+    assert result == []\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=False)\n+def test_detect_concurrent_is_noop_off_windows(_winp, tmp_path):\n+    \"\"\"No process enumeration off-Windows; the file-lock issue is Windows-only.\"\"\"\n+    assert cli_main._detect_concurrent_hermes_instances(tmp_path) == []\n+\n+\n+# ---------------------------------------------------------------------------\n+# _format_concurrent_instances_message\n+# ---------------------------------------------------------------------------\n+\n+\n+def test_format_message_mentions_pids_and_remediation(tmp_path):\n+    matches = [(1234, \"hermes.exe\"), (5678, \"hermes.exe\")]\n+    msg = cli_main._format_concurrent_instances_message(matches, tmp_path)\n+\n+    assert \"1234\" in msg\n+    assert \"5678\" in msg\n+    assert \"hermes.exe\" in msg\n+    assert \"Hermes Desktop\" in msg\n+    assert \"--force\" in msg\n+    # Mentions the file that would have been overwritten\n+    assert str(tmp_path / \"hermes.exe\") in msg\n+\n+\n+# ---------------------------------------------------------------------------\n+# _quarantine_running_hermes_exe — retry + reboot-deferred fallback\n+# ---------------------------------------------------------------------------\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_quarantine_succeeds_first_attempt(_winp, tmp_path):\n+    \"\"\"When the rename works immediately, no warning, single rename pair returned.\"\"\"\n+    shim = tmp_path / \"hermes.exe\"\n+    shim.write_bytes(b\"old\")\n+\n+    pairs = cli_main._quarantine_running_hermes_exe(tmp_path)\n+\n+    assert len(pairs) == 1\n+    orig, quarantine = pairs[0]\n+    assert orig == shim\n+    assert quarantine.name.startswith(\"hermes.exe.old.\")\n+    assert quarantine.exists()\n+    assert not shim.exists()\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_quarantine_retries_then_succeeds(_winp, tmp_path, monkeypatch):\n+    \"\"\"A transient OSError on the first attempt should not be fatal.\"\"\"\n+    shim = tmp_path / \"hermes.exe\"\n+    shim.write_bytes(b\"old\")\n+\n+    original_rename = Path.rename\n+    call_count = {\"n\": 0}\n+\n+    def flaky_rename(self, target):\n+        call_count[\"n\"] += 1\n+        if call_count[\"n\"] == 1:\n+            raise OSError(32, \"share violation (simulated AV scan)\")\n+        return original_rename(self, target)\n+\n+    # Speed up the test: avoid actual sleeps in the backoff schedule.\n+    monkeypatch.setattr(cli_main, \"_hermes_exe_shims\", lambda d: [shim])\n+    with patch.object(Path, \"rename\", flaky_rename), patch(\n+        \"time.sleep\", lambda *_a, **_k: None\n+    ):\n+        pairs = cli_main._quarantine_running_hermes_exe(tmp_path)\n+\n+    assert call_count[\"n\"] >= 2\n+    assert len(pairs) == 1\n+    assert not shim.exists()\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_quarantine_falls_back_to_reboot_schedule(_winp, tmp_path, capsys, monkeypatch):\n+    \"\"\"When every retry fails, we schedule via MoveFileEx and warn helpfully.\"\"\"\n+    shim = tmp_path / \"hermes.exe\"\n+    shim.write_bytes(b\"locked\")\n+\n+    def always_fails(self, target):\n+        raise OSError(32, \"The process cannot access the file (simulated lock)\")\n+\n+    scheduled_calls: list[tuple[Path, Path]] = []\n+\n+    def fake_schedule(s: Path, q: Path) -> bool:\n+        scheduled_calls.append((s, q))\n+        return True\n+\n+    monkeypatch.setattr(cli_main, \"_hermes_exe_shims\", lambda d: [shim])\n+    with patch.object(Path, \"rename\", always_fails), patch.object(\n+        cli_main, \"_schedule_replace_on_reboot\", fake_schedule\n+    ), patch(\"time.sleep\", lambda *_a, **_k: None):\n+        pairs = cli_main._quarantine_running_hermes_exe(tmp_path)\n+\n+    captured = capsys.readouterr().out\n+\n+    # The reboot-deferred path was used.\n+    assert scheduled_calls and scheduled_calls[0][0] == shim\n+    # It is NOT added to the returned roll-back list (the issue calls this\n+    # out — don't undo a deferred operation).\n+    assert pairs == []\n+    # The user got a clear message, not raw [WinError 32].\n+    assert \"scheduled\" in captured.lower()\n+    assert \"reboot\" in captured.lower()\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_quarantine_actionable_warning_when_everything_fails(\n+    _winp, tmp_path, capsys, monkeypatch\n+):\n+    \"\"\"When even MoveFileEx fails we should print remediation hints, not a bare error.\"\"\"\n+    shim = tmp_path / \"hermes.exe\"\n+    shim.write_bytes(b\"locked\")\n+\n+    def always_fails(self, target):\n+        raise OSError(32, \"share violation\")\n+\n+    monkeypatch.setattr(cli_main, \"_hermes_exe_shims\", lambda d: [shim])\n+    with patch.object(Path, \"rename\", always_fails), patch.object(\n+        cli_main, \"_schedule_replace_on_reboot\", lambda *_a, **_k: False\n+    ), patch(\"time.sleep\", lambda *_a, **_k: None):\n+        pairs = cli_main._quarantine_running_hermes_exe(tmp_path)\n+\n+    captured = capsys.readouterr().out\n+    assert pairs == []\n+    # New message format: no raw \"[WinError 32]\" dump; instead names the cause\n+    # and tells the user what to do.\n+    assert \"another process\" in captured.lower()\n+    assert \"Hermes Desktop\" in captured or \"gateway\" in captured.lower()\n+\n+\n+# ---------------------------------------------------------------------------\n+# cmd_update integration — concurrent-instance gate\n+# ---------------------------------------------------------------------------\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_cmd_update_aborts_on_concurrent_instance(_winp, tmp_path, capsys):\n+    \"\"\"If another hermes.exe is running, the update bails out before\n+    touching the working tree (exit code 2).\"\"\"\n+    scripts_dir = tmp_path / \"Scripts\"\n+    scripts_dir.mkdir()\n+\n+    args = SimpleNamespace(\n+        check=False,\n+        gateway=False,\n+        yes=False,\n+        force=False,\n+        backup=False,\n+        no_backup=True,\n+    )\n+\n+    with patch.object(\n+        cli_main, \"_venv_scripts_dir\", return_value=scripts_dir\n+    ), patch.object(\n+        cli_main,\n+        \"_detect_concurrent_hermes_instances\",\n+        return_value=[(4242, \"hermes.exe\")],\n+    ), patch.object(\n+        cli_main, \"_run_pre_update_backup\"\n+    ) as mock_backup, patch.object(\n+        cli_main, \"_install_hangup_protection\", return_value={}\n+    ), patch.object(\n+        cli_main, \"_finalize_update_output\"\n+    ):\n+        with pytest.raises(SystemExit) as excinfo:\n+            cli_main.cmd_update(args)\n+\n+    assert excinfo.value.code == 2\n+    # The pre-update backup runs AFTER the concurrent check; should not have\n+    # been invoked.\n+    mock_backup.assert_not_called()\n+\n+    captured = capsys.readouterr().out\n+    assert \"4242\" in captured\n+    assert \"--force\" in captured\n+\n+\n[redacted:email](cli_main, \"_is_windows\", return_value=True)\n+def test_cmd_update_force_bypasses_concurrent_check(_winp, tmp_path):\n+    \"\"\"--force lets the update proceed past the concurrent-instance gate\n+    (subsequent steps are mocked so we only verify the gate is skipped).\"\"\"\n+    scripts_dir = tmp_path / \"Scripts\"\n+    scripts_dir.mkdir()\n+\n+    args = SimpleNamespace(\n+        check=False,\n+        gateway=False,\n+        yes=False,\n+        force=True,  # ← the bypass\n+        backup=False,\n+        no_backup=True,\n+    )\n+\n+    detect = MagicMock(return_value=[(9, \"hermes.exe\")])\n+\n+    # Short-circuit out of _cmd_update_impl via a sentinel raise immediately\n+    # AFTER the gate. _run_pre_update_backup is the first call after the gate.\n+    sentinel = RuntimeError(\"reached post-gate body\")\n+    with patch.object(\n+        cli_main, \"_venv_scripts_dir\", return_value=scripts_dir\n+    ), patch.object(\n+        cli_main, \"_detect_concurrent_hermes_instances\", detect\n+    ), patch.object(\n+        cli_main, \"_run_pre_update_backup\", side_effect=sentinel\n+    ), patch.object(\n+        cli_main, \"_install_hangup_protection\", return_value={}\n+    ), patch.object(\n+        cli_main, \"_finalize_update_output\"\n+    ):\n+        with pytest.raises(RuntimeError, match=\"reached post-gate body\"):\n+            cli_main.cmd_update(args)\n+\n+    # When --force is set, we should not have even consulted psutil.\n+    detect.assert_not_called()"]},{"file":"uv.lock","hunks":["@@ -40,7 +40,7 @@ wheels = [\n \n [[package]]\n name = \"aiohttp\"\n-version = \"3.13.4\"\n+version = \"3.13.3\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"aiohappyeyeballs\" },","@@ -51,93 +51,93 @@ dependencies = [\n     { name = \"propcache\" },\n     { name = \"yarl\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz\", hash = \"sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38\", size = 7859748, upload-time = \"2026-03-28T17:19:40.6Z\" }\n-wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/d4/7e/cb94129302d78c46662b47f9897d642fd0b33bdfef4b73b20c6ced35aa4c/aiohttp-3.13.4-cp311-cp311-macosx_10_9_universal2.whl\", hash = \"sha256:8ea0c64d1bcbf201b285c2246c51a0c035ba3bbd306640007bc5844a3b4658c1\", size = 760027, upload-time = \"2026-03-28T17:15:33.022Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/5e/cd/2db3c9397c3bd24216b203dd739945b04f8b87bb036c640da7ddb63c75ef/aiohttp-3.13.4-cp311-cp311-macosx_10_9_x86_64.whl\", hash = \"sha256:6f742e1fa45c0ed522b00ede565e18f97e4cf8d1883a712ac42d0339dfb0cce7\", size = 508325, upload-time = \"2026-03-28T17:15:34.714Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/36/a3/d28b2722ec13107f2e37a86b8a169897308bab6a3b9e071ecead9d67bd9b/aiohttp-3.13.4-cp311-cp311-macosx_11_0_arm64.whl\", hash = \"sha256:6dcfb50ee25b3b7a1222a9123be1f9f89e56e67636b561441f0b304e25aaef8f\", size = 502402, upload-time = \"2026-03-28T17:15:36.409Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/fa/d6/acd47b5f17c4430e555590990a4746efbcb2079909bb865516892bf85f37/aiohttp-3.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:3262386c4ff370849863ea93b9ea60fd59c6cf56bf8f93beac625cf4d677c04d\", size = 1771224, upload-time = \"2026-03-28T17:15:38.223Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/98/af/af6e20113ba6a48fd1cd9e5832c4851e7613ef50c7619acdaee6ec5f1aff/aiohttp-3.13.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:473bb5aa4218dd254e9ae4834f20e31f5a0083064ac0136a01a62ddbae2eaa42\", size = 1731530, upload-time = \"2026-03-28T17:15:39.988Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/81/16/78a2f5d9c124ad05d5ce59a9af94214b6466c3491a25fb70760e98e9f762/aiohttp-3.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:e56423766399b4c77b965f6aaab6c9546617b8994a956821cc507d00b91d978c\", size = 1827925, upload-time = \"2026-03-28T17:15:41.944Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/2a/1f/79acf0974ced805e0e70027389fccbb7d728e6f30fcac725fb1071e63075/aiohttp-3.13.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:8af249343fafd5ad90366a16d230fc265cf1149f26075dc9fe93cfd7c7173942\", size = 1923579, upload-time = \"2026-03-28T17:15:44.071Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/af/53/29f9e2054ea6900413f3b4c3eb9d8331f60678ec855f13ba8714c47fd48d/aiohttp-3.13.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:0bc0a5cf4f10ef5a2c94fdde488734b582a3a7a000b131263e27c9295bd682d9\", size = 1767655, upload-time = \"2026-03-28T17:15:45.911Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/f3/57/462fe1d3da08109ba4aa8590e7aed57c059af2a7e80ec21f4bac5cfe1094/aiohttp-3.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:5c7ff1028e3c9fc5123a865ce17df1cb6424d180c503b8517afbe89aa566e6be\", size = 1630439, upload-time = \"2026-03-28T17:15:48.11Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d7/4b/4813344aacdb8127263e3eec343d24e973421143826364fa9fc847f6283f/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_aarch64.whl\", hash = \"sha256:ba5cf98b5dcb9bddd857da6713a503fa6d341043258ca823f0f5ab7ab4a94ee8\", size = 1745557, upload-time = \"2026-03-28T17:15:50.13Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d4/01/1ef1adae1454341ec50a789f03cfafe4c4ac9c003f6a64515ecd32fe4210/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_armv7l.whl\", hash = \"sha256:d85965d3ba21ee4999e83e992fecb86c4614d6920e40705501c0a1f80a583c12\", size = 1741796, upload-time = \"2026-03-28T17:15:52.351Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/22/04/8cdd99af988d2aa6922714d957d21383c559835cbd43fbf5a47ddf2e0f05/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl\", hash = \"sha256:49f0b18a9b05d79f6f37ddd567695943fcefb834ef480f17a4211987302b2dc7\", size = 1805312, upload-time = \"2026-03-28T17:15:54.407Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/fb/7f/b48d5577338d4b25bbdbae35c75dbfd0493cb8886dc586fbfb2e90862239/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_riscv64.whl\", hash = \"sha256:7f78cb080c86fbf765920e5f1ef35af3f24ec4314d6675d0a21eaf41f6f2679c\", size = 1621751, upload-time = \"2026-03-28T17:15:56.564Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/bc/89/4eecad8c1858e6d0893c05929e22343e0ebe3aec29a8a399c65c3cc38311/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_s390x.whl\", hash = \"sha256:67a3ec705534a614b68bbf1c70efa777a21c3da3895d1c44510a41f5a7ae0453\", size = 1826073, upload-time = \"2026-03-28T17:15:58.489Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/f5/5c/9dc8293ed31b46c39c9c513ac7ca152b3c3d38e0ea111a530ad12001b827/aiohttp-3.13.4-cp311-cp311-musllinux_1_2_x86_64.whl\", hash = \"sha256:d6630ec917e85c5356b2295744c8a97d40f007f96a1c76bf1928dc2e27465393\", size = 1760083, upload-time = \"2026-03-28T17:16:00.677Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/1e/19/8bbf6a4994205d96831f97b7d21a0feed120136e6267b5b22d229c6dc4dc/aiohttp-3.13.4-cp311-cp311-win32.whl\", hash = \"sha256:54049021bc626f53a5394c29e8c444f726ee5a14b6e89e0ad118315b1f90f5e3\", size = 439690, upload-time = \"2026-03-28T17:16:02.902Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/0c/f5/ac409ecd1007528d15c3e8c3a57d34f334c70d76cfb7128a28cffdebd4c1/aiohttp-3.13.4-cp311-cp311-win_amd64.whl\", hash = \"sha256:c033f2bc964156030772d31cbf7e5defea181238ce1f87b9455b786de7d30145\", size = 463824, upload-time = \"2026-03-28T17:16:05.058Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl\", hash = \"sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360\", size = 752158, upload-time = \"2026-03-28T17:16:06.901Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl\", hash = \"sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d\", size = 501037, upload-time = \"2026-03-28T17:16:08.82Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl\", hash = \"sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c\", size = 501556, upload-time = \"2026-03-28T17:16:10.63Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97\", size = 1757314, upload-time = \"2026-03-28T17:16:12.498Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576\", size = 1731819, upload-time = \"2026-03-28T17:16:14.558Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab\", size = 1793279, upload-time = \"2026-03-28T17:16:16.594Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d\", size = 1891082, upload-time = \"2026-03-28T17:16:18.71Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e\", size = 1773938, upload-time = \"2026-03-28T17:16:21.125Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3\", size = 1579548, upload-time = \"2026-03-28T17:16:23.588Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl\", hash = \"sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83\", size = 1714669, upload-time = \"2026-03-28T17:16:25.713Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl\", hash = \"sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763\", size = 1754175, upload-time = \"2026-03-28T17:16:28.18Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl\", hash = \"sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9\", size = 1762049, upload-time = \"2026-03-28T17:16:30.941Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl\", hash = \"sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758\", size = 1570861, upload-time = \"2026-03-28T17:16:32.953Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl\", hash = \"sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9\", size = 1790003, upload-time = \"2026-03-28T17:16:35.468Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl\", hash = \"sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d\", size = 1737289, upload-time = \"2026-03-28T17:16:38.187Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl\", hash = \"sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791\", size = 434185, upload-time = \"2026-03-28T17:16:40.735Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl\", hash = \"sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77\", size = 461285, upload-time = \"2026-03-28T17:16:42.713Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl\", hash = \"sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538\", size = 745744, upload-time = \"2026-03-28T17:16:44.67Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e\", size = 498178, upload-time = \"2026-03-28T17:16:46.766Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3\", size = 498331, upload-time = \"2026-03-28T17:16:48.9Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2\", size = 1744414, upload-time = \"2026-03-28T17:16:50.968Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21\", size = 1719226, upload-time = \"2026-03-28T17:16:53.173Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0\", size = 1782110, upload-time = \"2026-03-28T17:16:55.362Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e\", size = 1884809, upload-time = \"2026-03-28T17:16:57.734Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a\", size = 1764938, upload-time = \"2026-03-28T17:17:00.221Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f\", size = 1570697, upload-time = \"2026-03-28T17:17:02.985Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c\", size = 1702258, upload-time = \"2026-03-28T17:17:05.28Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl\", hash = \"sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500\", size = 1740287, upload-time = \"2026-03-28T17:17:07.712Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl\", hash = \"sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7\", size = 1753011, upload-time = \"2026-03-28T17:17:10.053Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl\", hash = \"sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073\", size = 1566359, upload-time = \"2026-03-28T17:17:12.433Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl\", hash = \"sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069\", size = 1785537, upload-time = \"2026-03-28T17:17:14.721Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5\", size = 1740752, upload-time = \"2026-03-28T17:17:17.387Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl\", hash = \"sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70\", size = 433187, upload-time = \"2026-03-28T17:17:19.523Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl\", hash = \"sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3\", size = 459778, upload-time = \"2026-03-28T17:17:21.964Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl\", hash = \"sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57\", size = 749363, upload-time = \"2026-03-28T17:17:24.044Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl\", hash = \"sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933\", size = 499317, upload-time = \"2026-03-28T17:17:26.199Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl\", hash = \"sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7\", size = 500477, upload-time = \"2026-03-28T17:17:28.279Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c\", size = 1737227, upload-time = \"2026-03-28T17:17:30.587Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349\", size = 1694036, upload-time = \"2026-03-28T17:17:33.29Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329\", size = 1786814, upload-time = \"2026-03-28T17:17:36.035Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde\", size = 1866676, upload-time = \"2026-03-28T17:17:38.441Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed\", size = 1740842, upload-time = \"2026-03-28T17:17:40.783Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f\", size = 1566508, upload-time = \"2026-03-28T17:17:43.235Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl\", hash = \"sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a\", size = 1700569, upload-time = \"2026-03-28T17:17:46.165Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl\", hash = \"sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b\", size = 1707407, upload-time = \"2026-03-28T17:17:48.825Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl\", hash = \"sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2\", size = 1752214, upload-time = \"2026-03-28T17:17:51.206Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl\", hash = \"sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e\", size = 1562162, upload-time = \"2026-03-28T17:17:53.634Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl\", hash = \"sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb\", size = 1768904, upload-time = \"2026-03-28T17:17:55.991Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl\", hash = \"sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165\", size = 1723378, upload-time = \"2026-03-28T17:17:58.348Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl\", hash = \"sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9\", size = 438711, upload-time = \"2026-03-28T17:18:00.728Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl\", hash = \"sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8\", size = 464977, upload-time = \"2026-03-28T17:18:03.367Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl\", hash = \"sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1\", size = 781549, upload-time = \"2026-03-28T17:18:05.779Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl\", hash = \"sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c\", size = 514383, upload-time = \"2026-03-28T17:18:08.096Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl\", hash = \"sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954\", size = 518304, upload-time = \"2026-03-28T17:18:10.642Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551\", size = 1893433, upload-time = \"2026-03-28T17:18:13.121Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871\", size = 1755901, upload-time = \"2026-03-28T17:18:16.21Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e\", size = 1876093, upload-time = \"2026-03-28T17:18:18.703Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8\", size = 1970815, upload-time = \"2026-03-28T17:18:21.858Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27\", size = 1816223, upload-time = \"2026-03-28T17:18:24.729Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7\", size = 1649145, upload-time = \"2026-03-28T17:18:27.269Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl\", hash = \"sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb\", size = 1816562, upload-time = \"2026-03-28T17:18:29.847Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl\", hash = \"sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927\", size = 1800333, upload-time = \"2026-03-28T17:18:32.269Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl\", hash = \"sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965\", size = 1820617, upload-time = \"2026-03-28T17:18:35.238Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl\", hash = \"sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182\", size = 1643417, upload-time = \"2026-03-28T17:18:37.949Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl\", hash = \"sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b\", size = 1849286, upload-time = \"2026-03-28T17:18:40.534Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl\", hash = \"sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba\", size = 1782635, upload-time = \"2026-03-28T17:18:43.073Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl\", hash = \"sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30\", size = 472537, upload-time = \"2026-03-28T17:18:46.286Z\" },\n-    { url = \"https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl\", hash = \"sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144\", size = 506381, upload-time = \"2026-03-28T17:18:48.74Z\" },\n+sdist = { url = \"https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz\", hash = \"sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88\", size = 7844556, upload-time = \"2026-01-03T17:33:05.204Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl\", hash = \"sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b\", size = 746051, upload-time = \"2026-01-03T17:29:43.287Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl\", hash = \"sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64\", size = 499234, upload-time = \"2026-01-03T17:29:44.822Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl\", hash = \"sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea\", size = 494979, upload-time = \"2026-01-03T17:29:46.405Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a\", size = 1748297, upload-time = \"2026-01-03T17:29:48.083Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540\", size = 1707172, upload-time = \"2026-01-03T17:29:49.648Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b\", size = 1805405, upload-time = \"2026-01-03T17:29:51.244Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3\", size = 1899449, upload-time = \"2026-01-03T17:29:53.938Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1\", size = 1748444, upload-time = \"2026-01-03T17:29:55.484Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3\", size = 1606038, upload-time = \"2026-01-03T17:29:57.179Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl\", hash = \"sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440\", size = 1724156, upload-time = \"2026-01-03T17:29:58.914Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl\", hash = \"sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7\", size = 1722340, upload-time = \"2026-01-03T17:30:01.962Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl\", hash = \"sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c\", size = 1783041, upload-time = \"2026-01-03T17:30:03.609Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl\", hash = \"sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51\", size = 1596024, upload-time = \"2026-01-03T17:30:05.132Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl\", hash = \"sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4\", size = 1804590, upload-time = \"2026-01-03T17:30:07.135Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl\", hash = \"sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29\", size = 1740355, upload-time = \"2026-01-03T17:30:09.083Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl\", hash = \"sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239\", size = 433701, upload-time = \"2026-01-03T17:30:10.869Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl\", hash = \"sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f\", size = 457678, upload-time = \"2026-01-03T17:30:12.719Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl\", hash = \"sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c\", size = 739732, upload-time = \"2026-01-03T17:30:14.23Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl\", hash = \"sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168\", size = 494293, upload-time = \"2026-01-03T17:30:15.96Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl\", hash = \"sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d\", size = 493533, upload-time = \"2026-01-03T17:30:17.431Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29\", size = 1737839, upload-time = \"2026-01-03T17:30:19.422Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3\", size = 1703932, upload-time = \"2026-01-03T17:30:21.756Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d\", size = 1771906, upload-time = \"2026-01-03T17:30:23.932Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463\", size = 1871020, upload-time = \"2026-01-03T17:30:26Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc\", size = 1755181, upload-time = \"2026-01-03T17:30:27.554Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf\", size = 1561794, upload-time = \"2026-01-03T17:30:29.254Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl\", hash = \"sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033\", size = 1697900, upload-time = \"2026-01-03T17:30:31.033Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl\", hash = \"sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f\", size = 1728239, upload-time = \"2026-01-03T17:30:32.703Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl\", hash = \"sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679\", size = 1740527, upload-time = \"2026-01-03T17:30:34.695Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl\", hash = \"sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423\", size = 1554489, upload-time = \"2026-01-03T17:30:36.864Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl\", hash = \"sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce\", size = 1767852, upload-time = \"2026-01-03T17:30:39.433Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl\", hash = \"sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a\", size = 1722379, upload-time = \"2026-01-03T17:30:41.081Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl\", hash = \"sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046\", size = 428253, upload-time = \"2026-01-03T17:30:42.644Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl\", hash = \"sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57\", size = 455407, upload-time = \"2026-01-03T17:30:44.195Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl\", hash = \"sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c\", size = 734190, upload-time = \"2026-01-03T17:30:45.832Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9\", size = 491783, upload-time = \"2026-01-03T17:30:47.466Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3\", size = 490704, upload-time = \"2026-01-03T17:30:49.373Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf\", size = 1720652, upload-time = \"2026-01-03T17:30:50.974Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6\", size = 1692014, upload-time = \"2026-01-03T17:30:52.729Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d\", size = 1759777, upload-time = \"2026-01-03T17:30:54.537Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261\", size = 1861276, upload-time = \"2026-01-03T17:30:56.512Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0\", size = 1743131, upload-time = \"2026-01-03T17:30:58.256Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730\", size = 1556863, upload-time = \"2026-01-03T17:31:00.445Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91\", size = 1682793, upload-time = \"2026-01-03T17:31:03.024Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl\", hash = \"sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3\", size = 1716676, upload-time = \"2026-01-03T17:31:04.842Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl\", hash = \"sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4\", size = 1733217, upload-time = \"2026-01-03T17:31:06.868Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl\", hash = \"sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998\", size = 1552303, upload-time = \"2026-01-03T17:31:08.958Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl\", hash = \"sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0\", size = 1763673, upload-time = \"2026-01-03T17:31:10.676Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591\", size = 1721120, upload-time = \"2026-01-03T17:31:12.575Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl\", hash = \"sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf\", size = 427383, upload-time = \"2026-01-03T17:31:14.382Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl\", hash = \"sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e\", size = 453899, upload-time = \"2026-01-03T17:31:15.958Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl\", hash = \"sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808\", size = 737238, upload-time = \"2026-01-03T17:31:17.909Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl\", hash = \"sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415\", size = 492292, upload-time = \"2026-01-03T17:31:19.919Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl\", hash = \"sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f\", size = 493021, upload-time = \"2026-01-03T17:31:21.636Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6\", size = 1717263, upload-time = \"2026-01-03T17:31:23.296Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687\", size = 1669107, upload-time = \"2026-01-03T17:31:25.334Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26\", size = 1760196, upload-time = \"2026-01-03T17:31:27.394Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a\", size = 1843591, upload-time = \"2026-01-03T17:31:29.238Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1\", size = 1720277, upload-time = \"2026-01-03T17:31:31.053Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25\", size = 1548575, upload-time = \"2026-01-03T17:31:32.87Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl\", hash = \"sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603\", size = 1679455, upload-time = \"2026-01-03T17:31:34.76Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl\", hash = \"sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a\", size = 1687417, upload-time = \"2026-01-03T17:31:36.699Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl\", hash = \"sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926\", size = 1729968, upload-time = \"2026-01-03T17:31:38.622Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl\", hash = \"sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba\", size = 1545690, upload-time = \"2026-01-03T17:31:40.57Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl\", hash = \"sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c\", size = 1746390, upload-time = \"2026-01-03T17:31:42.857Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl\", hash = \"sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43\", size = 1702188, upload-time = \"2026-01-03T17:31:44.984Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl\", hash = \"sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1\", size = 433126, upload-time = \"2026-01-03T17:31:47.463Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl\", hash = \"sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984\", size = 459128, upload-time = \"2026-01-03T17:31:49.2Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl\", hash = \"sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c\", size = 769512, upload-time = \"2026-01-03T17:31:51.134Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl\", hash = \"sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592\", size = 506444, upload-time = \"2026-01-03T17:31:52.85Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl\", hash = \"sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f\", size = 510798, upload-time = \"2026-01-03T17:31:54.91Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29\", size = 1868835, upload-time = \"2026-01-03T17:31:56.733Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl\", hash = \"sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc\", size = 1720486, upload-time = \"2026-01-03T17:31:58.65Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl\", hash = \"sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2\", size = 1847951, upload-time = \"2026-01-03T17:32:00.989Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl\", hash = \"sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587\", size = 1941001, upload-time = \"2026-01-03T17:32:03.122Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8\", size = 1797246, upload-time = \"2026-01-03T17:32:05.255Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl\", hash = \"sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632\", size = 1627131, upload-time = \"2026-01-03T17:32:07.607Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl\", hash = \"sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64\", size = 1795196, upload-time = \"2026-01-03T17:32:09.59Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl\", hash = \"sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0\", size = 1782841, upload-time = \"2026-01-03T17:32:11.445Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl\", hash = \"sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56\", size = 1795193, upload-time = \"2026-01-03T17:32:13.705Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl\", hash = \"sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72\", size = 1621979, upload-time = \"2026-01-03T17:32:15.965Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl\", hash = \"sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df\", size = 1822193, upload-time = \"2026-01-03T17:32:18.219Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl\", hash = \"sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa\", size = 1769801, upload-time = \"2026-01-03T17:32:20.25Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl\", hash = \"sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767\", size = 466523, upload-time = \"2026-01-03T17:32:22.215Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl\", hash = \"sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344\", size = 499694, upload-time = \"2026-01-03T17:32:24.546Z\" },\n ]\n \n [[package]]","@@ -321,7 +321,7 @@ wheels = [\n \n [[package]]\n name = \"anthropic\"\n-version = \"0.87.0\"\n+version = \"0.86.0\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"anyio\" },","@@ -333,9 +333,9 @@ dependencies = [\n     { name = \"sniffio\" },\n     { name = \"typing-extensions\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/d6/8f/3281edf7c35cbac169810e5388eb9b38678c7ea9867c2d331237bd5dff08/anthropic-0.87.0.tar.gz\", hash = \"sha256:098fef3753cdd3c0daa86f95efb9c8d03a798d45c5170329525bb4653f6702d0\", size = 588982, upload-time = \"2026-03-31T17:52:41.697Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz\", hash = \"sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5\", size = 583820, upload-time = \"2026-03-18T18:43:08.017Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/0d/02/99bf351933bdea0545a2b6e2d812ed878899e9a95f618351dfa3d0de0e69/anthropic-0.87.0-py3-none-any.whl\", hash = \"sha256:e2669b86d42c739d3df163f873c51719552e263a3d85179297180fb4fa00a236\", size = 472126, upload-time = \"2026-03-31T17:52:40.174Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl\", hash = \"sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57\", size = 469400, upload-time = \"2026-03-18T18:43:06.526Z\" },\n ]\n \n [[package]]","@@ -1569,11 +1569,10 @@ wheels = [\n \n [[package]]\n name = \"hermes-agent\"\n-version = \"0.13.0\"\n+version = \"0.14.0\"\n source = { editable = \".\" }\n dependencies = [\n     { name = \"croniter\" },\n-    { name = \"cryptography\" },\n     { name = \"fire\" },\n     { name = \"httpx\", extra = [\"socks\"] },\n     { name = \"jinja2\" },","@@ -1759,19 +1758,18 @@ youtube = [\n [package.metadata]\n requires-dist = [\n     { name = \"agent-client-protocol\", marker = \"extra == 'acp'\", specifier = \"==0.9.0\" },\n-    { name = \"aiohttp\", marker = \"extra == 'homeassistant'\", specifier = \"==3.13.4\" },\n-    { name = \"aiohttp\", marker = \"extra == 'messaging'\", specifier = \"==3.13.4\" },\n-    { name = \"aiohttp\", marker = \"extra == 'slack'\", specifier = \"==3.13.4\" },\n-    { name = \"aiohttp\", marker = \"extra == 'sms'\", specifier = \"==3.13.4\" },\n+    { name = \"aiohttp\", marker = \"extra == 'homeassistant'\", specifier = \"==3.13.3\" },\n+    { name = \"aiohttp\", marker = \"extra == 'messaging'\", specifier = \"==3.13.3\" },\n+    { name = \"aiohttp\", marker = \"extra == 'slack'\", specifier = \"==3.13.3\" },\n+    { name = \"aiohttp\", marker = \"extra == 'sms'\", specifier = \"==3.13.3\" },\n     { name = \"aiohttp-socks\", marker = \"extra == 'matrix'\", specifier = \"==0.11.0\" },\n     { name = \"aiosqlite\", marker = \"extra == 'matrix'\", specifier = \"==0.22.1\" },\n     { name = \"alibabacloud-dingtalk\", marker = \"extra == 'dingtalk'\", specifier = \"==2.2.42\" },\n-    { name = \"anthropic\", marker = \"extra == 'anthropic'\", specifier = \"==0.87.0\" },\n+    { name = \"anthropic\", marker = \"extra == 'anthropic'\", specifier = \"==0.86.0\" },\n     { name = \"asyncpg\", marker = \"extra == 'matrix'\", specifier = \"==0.31.0\" },\n     { name = \"boto3\", marker = \"extra == 'bedrock'\", specifier = \"==1.42.89\" },\n     { name = \"brotlicffi\", marker = \"extra == 'messaging'\", specifier = \"==1.2.0.1\" },\n     { name = \"croniter\", specifier = \"==6.0.0\" },\n-    { name = \"cryptography\", specifier = \"==46.0.7\" },\n     { name = \"daytona\", marker = \"extra == 'daytona'\", specifier = \"==0.155.0\" },\n     { name = \"debugpy\", marker = \"extra == 'dev'\", specifier = \"==1.8.20\" },\n     { name = \"dingtalk-stream\", marker = \"extra == 'dingtalk'\", specifier = \"==0.24.3\" },"]},{"file":"website/docs/getting-started/updating.md","hunks":["@@ -69,6 +69,26 @@ updates:\n \n `--backup` was the always-on behavior in earlier builds, but it was adding minutes to every update on large homes, so it's now opt-in. The lightweight pairing-data snapshot above still runs unconditionally.\n \n+### Windows: another `hermes.exe` is running\n+\n+On Windows, `hermes update` will refuse to run if it detects another `hermes.exe` process holding the venv's entry-point executable open — most commonly the Hermes Desktop app's spawned backend, an open `hermes` REPL in another terminal, or a running gateway:\n+\n+```\n+$ hermes update\n+✗ Another hermes.exe is running:\n+    PID 12345  hermes.exe\n+\n+  Updating now would fail to overwrite ...\\venv\\Scripts\\hermes.exe because\n+  Windows blocks REPLACE on a running executable.\n+\n+  Close Hermes Desktop, exit any open `hermes` REPLs, and\n+  stop the gateway (`hermes gateway stop`) before retrying.\n+  Override with `hermes update --force` if you've already\n+  confirmed those processes will not write to the venv.\n+```\n+\n+Close the listed processes and re-run. If you're sure the concurrent process won't interfere (rare — usually only useful when an antivirus shim is mis-attributed), pass `--force` to skip the check. In that case the updater will still retry the `.exe` rename with exponential backoff and, on stubborn locks, schedule the replacement for next reboot via `MoveFileEx(MOVEFILE_DELAY_UNTIL_REBOOT)` so the update can complete.\n+\n Expected output looks like:\n \n ```\n"]}]},{"kind":"pr","number":37790,"title":"fix(installer): reap Windows update locker processes","url":null,"state":"open","close_reason":{"refs":[{"kind":"pr","value":"#37731","number":37731}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":39037,"title":"fix(cli): detect venv python workers before update to prevent .pyd lock failures (#38789)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"b14d75f8afdb624be03cbefd19494ccc850bf367","number":0},{"kind":"commit","value":"87ae4ae94bc13e302be2a37b30226f2b19909227","number":0},{"kind":"pr","value":"#38789","number":38789}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":39487,"title":"fix(update): pause Windows gateway before venv refresh","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"78c11d99e35b111dd6198d8bc6aa1f7f6f168232","number":0},{"kind":"pr","value":"#45457","number":45457}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":42100,"title":"fix(desktop): stop running Hermes.exe locking win-unpacked before Windows pack","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/main.py","tests/hermes_cli/test_gui_command.py"],"fix_hunks":[{"file":"hermes_cli/main.py","hunks":["@@ -7530,6 +7530,79 @@ def _purge_electron_build_cache(desktop_dir: Path) -> list[Path]:\n     return removed\n \n \n+def _stop_desktop_processes_locking_build(desktop_dir: Path) -> list[int]:\n+    \"\"\"Terminate any running desktop app executing from this build's ``release``\n+    dir so a rebuild can replace its (otherwise locked) executable.\n+\n+    On Windows a running ``Hermes.exe`` keeps an exclusive lock on\n+    ``release/win-unpacked/Hermes.exe``. electron-builder's pack then can't\n+    delete the stale binary and dies with ``remove …\\\\Hermes.exe: Access is\n+    denied`` / ``ERR_ELECTRON_BUILDER_CANNOT_EXECUTE`` (before-pack hits the same\n+    EPERM cleaning the dir). The retry path repeats the failure because the lock\n+    is still held. POSIX lets you unlink a running binary, so this is a no-op\n+    off-Windows.\n+\n+    Scope is deliberately narrow: only processes whose executable lives *inside*\n+    this desktop's ``release`` tree are stopped — a packaged install elsewhere or\n+    an unrelated \"Hermes\" process is never touched. Best-effort: never raises.\n+    Returns the PIDs we asked to stop.\n+    \"\"\"\n+    if sys.platform != \"win32\":\n+        return []\n+    try:\n+        import psutil\n+    except Exception:\n+        return []\n+    try:\n+        release_dir = (desktop_dir / \"release\").resolve()\n+    except OSError:\n+        return []\n+    if not release_dir.is_dir():\n+        return []\n+\n+    me = os.getpid()\n+    victims = []\n+    try:\n+        proc_iter = psutil.process_iter([\"pid\", \"exe\"])\n+    except Exception:\n+        return []\n+    for proc in proc_iter:\n+        try:\n+            info = proc.info\n+        except Exception:\n+            continue\n+        pid = info.get(\"pid\")\n+        exe = info.get(\"exe\")\n+        if not exe or pid is None or pid == me:\n+            continue\n+        try:\n+            exe_path = Path(exe).resolve()\n+        except (OSError, ValueError):\n+            continue\n+        if release_dir in exe_path.parents:\n+            victims.append(proc)\n+\n+    stopped: list[int] = []\n+    for proc in victims:\n+        try:\n+            proc.terminate()\n+            stopped.append(int(proc.pid))\n+        except Exception:\n+            continue\n+    if stopped:\n+        # Wait for the handles (and thus the file locks) to actually release.\n+        try:\n+            _, alive = psutil.wait_procs(victims, timeout=5)\n+            for proc in alive:\n+                try:\n+                    proc.kill()\n+                except Exception:\n+                    continue\n+        except Exception:\n+            pass\n+    return stopped\n+\n+\n def _desktop_macos_relaunchable_fixup(desktop_dir: Path) -> None:\n     \"\"\"Make a locally-built (unsigned) macOS desktop app survive in-place self-update.\n ","@@ -7686,6 +7759,15 @@ def cmd_gui(args: argparse.Namespace):\n             build_label = \"source build\" if source_mode else \"packaged app\"\n             print(f\"→ Building desktop {build_label}...\")\n             build_script = \"build\" if source_mode else \"pack\"\n+            if not source_mode:\n+                # A running desktop instance launched from release/win-unpacked\n+                # holds Hermes.exe locked on Windows, so the pack can't replace\n+                # it (\"Access is denied\" / ERR_ELECTRON_BUILDER_CANNOT_EXECUTE).\n+                # Stop it first so the rebuild — including the installer's\n+                # headless --update rebuild — succeeds instead of failing cryptically.\n+                stopped = _stop_desktop_processes_locking_build(desktop_dir)\n+                if stopped:\n+                    print(f\"  ⚠ Stopped running desktop app to free the build output (pid {', '.join(map(str, stopped))})\")\n             build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=env, check=False)\n             if build_result.returncode != 0 and not source_mode:\n                 # A corrupt cached Electron zip makes `pack` fail with an ENOENT","@@ -7706,10 +7788,16 @@ def cmd_gui(args: argparse.Namespace):\n                     print(\"  ⚠ Desktop build failed; cleared cached Electron download and retrying once...\")\n                     for p in purged:\n                         print(f\"    - {p}\")\n+                    # The purge can't remove a win-unpacked tree whose Hermes.exe\n+                    # is still locked by a running instance; stop it before retry.\n+                    _stop_desktop_processes_locking_build(desktop_dir)\n                     build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=env, check=False)\n             if build_result.returncode != 0:\n                 print(\"✗ Desktop GUI build failed\")\n                 print(f\"  Run manually:  cd apps/desktop && npm run {build_script}\")\n+                if sys.platform == \"win32\":\n+                    print(\"  If this says \\\"Access is denied\\\" on Hermes.exe, close any\")\n+                    print(\"  running Hermes desktop window and retry.\")\n                 sys.exit(build_result.returncode or 1)\n             packaged_executable = _desktop_packaged_executable(desktop_dir)\n             if not source_mode:"]},{"file":"tests/hermes_cli/test_gui_command.py","hunks":["@@ -519,3 +519,78 @@ def test_gui_does_not_retry_when_purge_finds_nothing(tmp_path, monkeypatch, caps\n     mock_purge.assert_called_once()\n     assert mock_run.call_count == 1\n     assert \"Desktop GUI build failed\" in capsys.readouterr().out\n+\n+\n+class _FakeProc:\n+    \"\"\"Minimal psutil.Process stand-in for the lock-breaker tests.\"\"\"\n+\n+    def __init__(self, pid: int, exe: str | None):\n+        self.pid = pid\n+        self.info = {\"pid\": pid, \"exe\": exe}\n+        self.terminated = False\n+        self.killed = False\n+\n+    def terminate(self):\n+        self.terminated = True\n+\n+    def kill(self):\n+        self.killed = True\n+\n+\n+def test_stop_desktop_build_lock_noop_off_windows(tmp_path, monkeypatch):\n+    \"\"\"POSIX can unlink a running binary, so the helper is a no-op there.\"\"\"\n+    desktop_dir = tmp_path / \"apps\" / \"desktop\"\n+    exe = desktop_dir / \"release\" / \"linux-unpacked\" / \"hermes\"\n+    exe.parent.mkdir(parents=True)\n+    exe.write_text(\"\", encoding=\"utf-8\")\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+\n+    proc = _FakeProc(4321, str(exe))\n+    with patch(\"psutil.process_iter\", return_value=[proc]) as it:\n+        assert cli_main._stop_desktop_processes_locking_build(desktop_dir) == []\n+    it.assert_not_called()\n+    assert proc.terminated is False\n+\n+\n+def test_stop_desktop_build_lock_terminates_only_release_procs(tmp_path, monkeypatch):\n+    desktop_dir = tmp_path / \"apps\" / \"desktop\"\n+    release = desktop_dir / \"release\" / \"win-unpacked\"\n+    release.mkdir(parents=True)\n+    locker_exe = release / \"Hermes.exe\"\n+    locker_exe.write_text(\"\", encoding=\"utf-8\")\n+    other_exe = tmp_path / \"elsewhere\" / \"Hermes.exe\"\n+    other_exe.parent.mkdir(parents=True)\n+    other_exe.write_text(\"\", encoding=\"utf-8\")\n+\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"win32\")\n+    monkeypatch.setattr(cli_main.os, \"getpid\", lambda: 999)\n+\n+    locker = _FakeProc(101, str(locker_exe))\n+    unrelated = _FakeProc(102, str(other_exe))\n+    selfish = _FakeProc(999, str(locker_exe))  # our own PID — never killed\n+    no_exe = _FakeProc(103, None)\n+\n+    captured = {}\n+\n+    def _wait(procs, timeout=None):\n+        captured[\"waited\"] = list(procs)\n+        return procs, []\n+\n+    with patch(\"psutil.process_iter\", return_value=[locker, unrelated, selfish, no_exe]), \\\n+         patch(\"psutil.wait_procs\", side_effect=_wait):\n+        stopped = cli_main._stop_desktop_processes_locking_build(desktop_dir)\n+\n+    assert stopped == [101]\n+    assert locker.terminated is True\n+    assert unrelated.terminated is False\n+    assert selfish.terminated is False\n+    assert captured[\"waited\"] == [locker]\n+\n+\n+def test_stop_desktop_build_lock_no_release_dir(tmp_path, monkeypatch):\n+    desktop_dir = tmp_path / \"apps\" / \"desktop\"\n+    desktop_dir.mkdir(parents=True)\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"win32\")\n+    with patch(\"psutil.process_iter\") as it:\n+        assert cli_main._stop_desktop_processes_locking_build(desktop_dir) == []\n+    it.assert_not_called()\n"]}]},{"kind":"pr","number":43381,"title":"fix(desktop): kill untracked hermes processes before update (#43268)","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":46726,"title":"fix(install): kill venv-resident gateway before recreating venv on Windows","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#52044","number":52044}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":47569,"title":"fix(install): clear venv python locks on Windows","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#46726","number":46726},{"kind":"pr","value":"#47557","number":47557},{"kind":"pr","value":"#45120","number":45120}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":47610,"title":"fix(installer): kill venv processes before removing venv dir (#47557)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#46726","number":46726},{"kind":"pr","value":"#47557","number":47557},{"kind":"pr","value":"#47569","number":47569}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":47621,"title":"fix(cli): kill venv python processes before venv recreation on Windows","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"6638199c53f64f995ac02253456f719960ec7673","number":0},{"kind":"pr","value":"#52044","number":52044},{"kind":"pr","value":"#47557","number":47557},{"kind":"pr","value":"#46726","number":46726}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":52044,"title":"fix(install): kill venv-resident gateway before recreating venv on Windows","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["scripts/install.ps1","scripts/release.py","tests/test_install_unmerged_index.py"],"fix_hunks":[{"file":"scripts/install.ps1","hunks":["@@ -1562,16 +1562,54 @@ function Install-Venv {\n     \n     if (Test-Path \"venv\") {\n         Write-Info \"Virtual environment already exists, recreating...\"\n-        # On Windows, native Python extensions (e.g. _bcrypt.pyd) are loaded as\n-        # DLLs by any running hermes process. Windows denies deletion of loaded\n-        # DLLs, so kill any hermes.exe tree before removing the venv.\n+        # On Windows, native Python extensions (e.g. _bcrypt.pyd, tornado's\n+        # speedups.pyd) are loaded as DLLs by any running hermes process.\n+        # Windows denies deletion of loaded DLLs, so every process running out\n+        # of this venv must be stopped before removing it -- otherwise\n+        # Remove-Item fails with \"Access to the path '...' is denied\" and the\n+        # whole install/update aborts at this stage.\n         if ($env:OS -eq \"Windows_NT\") {\n             $myPid = $PID\n             Write-Info \"Stopping any running hermes processes before recreating venv...\"\n+            # The launcher CLI (hermes.exe) plus its child tree.\n             & taskkill /F /T /IM hermes.exe /FI \"PID ne $myPid\" 2>$null | Out-Null\n+            # taskkill /IM hermes.exe is NOT enough: the gateway/agent that a\n+            # scheduled task or watchdog autostarts runs as\n+            # `pythonw.exe -m hermes_cli.main gateway run` straight out of\n+            # venv\\Scripts\\, so its image name is python/pythonw, not hermes.exe.\n+            # That process holds the venv's .pyd files open and re-triggers the\n+            # access-denied failure. Stop anything whose executable lives under\n+            # this venv, matched by path prefix so the image name does not matter\n+            # and a global/system python outside the venv is never touched.\n+            #\n+            # The gateway autostart task registers with /RL LIMITED as the current\n+            # user (see hermes_cli/gateway_windows.py), so the installer always\n+            # runs at equal-or-higher integrity and can read its executable path.\n+            # Get-CimInstance is used over Get-Process because it returns a null\n+            # ExecutablePath for a process it cannot inspect (a different session)\n+            # instead of throwing, so an unreadable process is skipped rather than\n+            # aborting the whole sweep.\n+            $venvPrefix = [System.IO.Path]::GetFullPath((Join-Path $InstallDir \"venv\")).TrimEnd('\\') + '\\'\n+            try {\n+                Get-CimInstance Win32_Process -ErrorAction Stop |\n+                    Where-Object { $_.ProcessId -ne $myPid -and $_.ExecutablePath -and $_.ExecutablePath.StartsWith($venvPrefix, [System.StringComparison]::OrdinalIgnoreCase) } |\n+                    ForEach-Object {\n+                        Write-Info \"  stopping PID $($_.ProcessId) ($($_.Name)) running from venv\"\n+                        Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue\n+                    }\n+            } catch {\n+                Write-Warn \"Could not enumerate venv processes: $($_.Exception.Message)\"\n+            }\n             Start-Sleep -Milliseconds 800\n         }\n-        Remove-Item -Recurse -Force \"venv\"\n+        Remove-Item -Recurse -Force \"venv\" -ErrorAction SilentlyContinue\n+        # A killed process can take a moment to release its file handles, so a\n+        # first Remove-Item may still hit a locked .pyd. Retry once after a short\n+        # pause before giving up and letting the stage fail loudly.\n+        if (Test-Path \"venv\") {\n+            Start-Sleep -Seconds 2\n+            Remove-Item -Recurse -Force \"venv\"\n+        }\n     }\n     \n     # uv creates the venv and pins the Python version in one step.  uv emits"]},{"file":"scripts/release.py","hunks":["@@ -45,6 +45,7 @@\n \n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n+    \"[redacted:email]\": \"Danamove\",  # PR #46726 salvage (kill venv-resident pythonw gateway before recreating venv on Windows; #47036/#47557/#47910)\n     \"145739220+wgu9@users.noreply.github.com\": \"wgu9\",  # PR #51468 salvage (WSL/no-systemd orphan gateway tracking, #51325)\n     \"165020384+uperLu@users.noreply.github.com\": \"uperLu\",  # PR #50958 salvage (rename plugins/cron → plugins/cron_providers; #50872)\n     \"277269729+yusekiotacode@users.noreply.github.com\": \"yusekiotacode\",  # PR #48706 salvage (anthropic OAuth login token endpoint → platform.claude.com; #45250/#49821)"]},{"file":"tests/test_install_unmerged_index.py","hunks":["@@ -141,3 +141,37 @@ def test_install_sh_clears_unmerged_index_before_stash_source_order() -> None:\n     idx_unmerged = text.index(\"ls-files --unmerged\")\n     idx_stash = text.index(\"stash push --include-untracked\")\n     assert idx_unmerged < idx_stash\n+\n+\n+def test_install_ps1_stops_venv_resident_processes_before_removing_venv() -> None:\n+    \"\"\"The Windows venv-recreate path must stop every process running out of the\n+    old venv before deleting it.\n+\n+    A gateway autostarted by a scheduled task runs as\n+    ``venv\\\\Scripts\\\\pythonw.exe -m hermes_cli.main gateway run`` — image name\n+    ``pythonw``, not ``hermes.exe`` — so the ``taskkill /IM hermes.exe`` guard\n+    misses it, the loaded ``.pyd`` stays locked, and ``Remove-Item venv`` fails\n+    mid-recursion (issues #47036/#47557/#47910). The recreate branch must also\n+    sweep by venv path prefix, and that sweep must run before the delete.\n+    \"\"\"\n+    text = INSTALL_PS1.read_text()\n+\n+    # The hermes.exe tree-kill is preserved (kills spawned child processes too).\n+    assert 'taskkill /F /T /IM hermes.exe' in text\n+\n+    # The venv path-prefix sweep exists. It must match by case-insensitive\n+    # StartsWith, NOT PowerShell -like: a venv path containing wildcard\n+    # metacharacters ('[', ']') — legal in a Windows user name — silently fails\n+    # to match under -like, reintroducing the exact miss this fix closes.\n+    idx_recreate = text.index(\"Virtual environment already exists, recreating\")\n+    idx_sweep = text.index(\"StartsWith($venvPrefix\", idx_recreate)\n+    assert \"[System.StringComparison]::OrdinalIgnoreCase\" in text[idx_sweep:idx_sweep + 200]\n+    assert 'ExecutablePath -like \"$venvRoot' not in text, (\n+        \"the -like wildcard match must not be used for venv path scoping\"\n+    )\n+\n+    # The process sweep must run before the venv is removed, or it is a no-op.\n+    idx_remove = text.index('Remove-Item -Recurse -Force \"venv\"', idx_recreate)\n+    assert idx_sweep < idx_remove, (\n+        \"venv-resident processes must be stopped before Remove-Item deletes the venv\"\n+    )\n"]}]},{"kind":"pr","number":57852,"title":"fix(update): ignore Windows system venv holder noise","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"1f89f3102f","number":0},{"kind":"commit","value":"50ea329ab4","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":64386,"title":"feat(cli): add --force-kill flag for Windows update (#63300)","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"3bfb6fb474e8","number":0},{"kind":"pr","value":"#64386","number":64386},{"kind":"pr","value":"#63300","number":63300},{"kind":"pr","value":"#374151","number":374151}],"category":"partial_fix","implication":"needs_llm"},"merged":false},{"kind":"pr","number":68821,"title":"fix(update): hard-stop when Windows hermes.exe shim stays locked","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#68760","number":68760}],"category":"stale_abandoned","implication":"active"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}