{"repo":"NousResearch/hermes-agent","public_id":"46785","display_label":"Desktop build: electronDist path and binary issues","summary":"Multiple bugs and proposed fixes concern the desktop build encountering an unresolvable electronDist path, stemming from Electron binary placement mismatches between workspace-local and repo-root node_modules, unreliable downloads, and installer misdiagnosis of related errors. Members span macOS, Windows, and Linux.","subsystem":"apps/bootstrap-installer","counts":{"members":13,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":9,"merged_prs":3,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":47276},"resolution":{"state":"partially_resolved","by":"pr-47276,pr-48081,pr-48091"},"freshness":{"state":"verified","last_verified_at":"2026-06-17T23:48:36+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.1148717948717949},"fix_edges":[{"issue_number":47266,"pr_number":47276,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":47917,"pr_number":48091,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":48059,"pr_number":48081,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":48084,"pr_number":48091,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":46785,"title":"Bootstrap installer misdiagnoses desktop build failures as Electron download problems","url":null,"state":"closed"},{"kind":"issue","number":47266,"title":"Windows install fails at desktop stage: electronDist does not exist (Electron download unreliable)","url":null,"state":"closed"},{"kind":"issue","number":47917,"title":"[Bug] Desktop build fails after update - electronDist does not exist (cache invalidated)","url":null,"state":"closed"},{"kind":"issue","number":48019,"title":"Windows desktop pack fails: electronDist points to repo-root node_modules instead of workspace-local Electron","url":null,"state":"closed"},{"kind":"issue","number":48021,"title":"Desktop installer: Electron binary not downloaded, build fails on fresh macOS install","url":null,"state":"closed"},{"kind":"issue","number":48059,"title":"[Bug]: macOS desktop build fails — `electronDist does not exist` because the lockfile installs `electron` under `apps/desktop/node_modules` while electron-builder expects it hoisted to the workspace root","url":null,"state":"closed"},{"kind":"issue","number":48084,"title":"bug(desktop): `electronDist` path mismatch — electron in workspace but path points to repo root","url":null,"state":"closed"},{"kind":"issue","number":56834,"title":"Desktop build fails on Linux with electron error","url":null,"state":"closed"},{"kind":"issue","number":67166,"title":"Installer misdiagnoses ENOSPC as a blocked Electron download, then burns more disk retrying","url":null,"state":"closed"},{"kind":"pr","number":47276,"title":"fix(desktop): re-download Electron binary via mirror when pack fails (#47266)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/main.py","scripts/install.ps1","scripts/install.sh","tests/hermes_cli/test_gui_command.py"],"fix_hunks":[{"file":"hermes_cli/main.py","hunks":["@@ -5110,6 +5110,90 @@ def _purge_electron_build_cache(desktop_dir: Path) -> list[Path]:\n     return removed\n \n \n+def _electron_dist_binary(project_root: Path) -> Path:\n+    \"\"\"Return the path to the Electron main binary inside ``node_modules``.\n+\n+    electron-builder reads the binary from ``build.electronDist``\n+    (``node_modules/electron/dist``) since #38673, so this is the exact file\n+    whose absence makes a pack fail with \"The specified electronDist does not\n+    exist\". The basename differs per OS (the platform Electron is named for the\n+    host the build runs on).\n+    \"\"\"\n+    dist = project_root / \"node_modules\" / \"electron\" / \"dist\"\n+    if sys.platform == \"darwin\":\n+        return dist / \"Electron.app\" / \"Contents\" / \"MacOS\" / \"Electron\"\n+    if sys.platform == \"win32\":\n+        return dist / \"electron.exe\"\n+    return dist / \"electron\"\n+\n+\n+def _electron_dist_ok(project_root: Path) -> bool:\n+    \"\"\"True when ``node_modules/electron/dist`` holds a usable Electron binary.\n+\n+    A directory that exists but is missing the binary (a partial extraction from\n+    a corrupt cached zip, or an interrupted postinstall) counts as NOT ok, since\n+    that is exactly the shape that makes electron-builder throw on the pinned\n+    electronDist.\n+    \"\"\"\n+    try:\n+        return _electron_dist_binary(project_root).exists()\n+    except OSError:\n+        return False\n+\n+\n+def _redownload_electron_dist(\n+    project_root: Path,\n+    env: dict,\n+    *,\n+    mirror: Optional[str] = None,\n+) -> bool:\n+    \"\"\"(Re)populate ``node_modules/electron/dist`` via electron's own downloader.\n+\n+    Since #38673 the desktop build pins ``build.electronDist`` to\n+    ``node_modules/electron/dist``, so electron-builder reads the Electron binary\n+    straight from there and never downloads it during ``npm run pack``. That dist\n+    tree is produced by the ``electron`` package's postinstall (``install.js``)\n+    during ``npm ci``. When that download is blocked or throttled (GitHub's\n+    release host is unreachable in some regions — #47266), the dist is missing\n+    and re-running ``pack`` only re-throws \"The specified electronDist does not\n+    exist\". The mirror fallback therefore has to drive *this* downloader, not\n+    another ``pack``.\n+\n+    No-op (returns True) when the dist binary is already present, so an unrelated\n+    build failure doesn't trigger a needless ~200 MB re-download. Otherwise drops\n+    any partial dist + version marker (electron's install.js short-circuits when\n+    ``path.txt`` already matches) and runs the downloader once, optionally via a\n+    mirror. Best-effort: never raises. Returns True iff the dist binary exists\n+    afterward.\n+    \"\"\"\n+    if _electron_dist_ok(project_root):\n+        return True\n+\n+    electron_dir = project_root / \"node_modules\" / \"electron\"\n+    installer = electron_dir / \"install.js\"\n+    if not installer.is_file():\n+        return False\n+    node = shutil.which(\"node\")\n+    if not node:\n+        return False\n+\n+    dist_dir = electron_dir / \"dist\"\n+    shutil.rmtree(dist_dir, ignore_errors=True)\n+    try:\n+        (electron_dir / \"path.txt\").unlink()\n+    except OSError:\n+        pass\n+\n+    dl_env = dict(env)\n+    if mirror:\n+        dl_env[\"ELECTRON_MIRROR\"] = mirror\n+    try:\n+        subprocess.run([node, str(installer)], cwd=str(electron_dir), env=dl_env, check=False)\n+    except OSError:\n+        return False\n+    return _electron_dist_ok(project_root)\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.","@@ -5364,8 +5448,18 @@ def cmd_gui(args: argparse.Namespace):\n                 # failure was something else, the clean re-download is harmless\n                 # and the retry fails the same way.\n                 purged = _purge_electron_build_cache(desktop_dir)\n-                if purged:\n-                    print(\"  ⚠ Desktop build failed; cleared cached Electron download and retrying once...\")\n+                # electronDist is pinned to node_modules/electron/dist (#38673):\n+                # electron-builder reads the Electron binary from there and `pack`\n+                # never downloads it, so purging the cache + re-running pack can't\n+                # by itself repopulate a missing/partial dist. When the dist is\n+                # actually gone, re-run electron's own downloader so the retry has\n+                # a binary to read. Gated on the dist check so an unrelated build\n+                # failure (tsc/vite) doesn't trigger a pointless ~200 MB refetch.\n+                restored = False\n+                if not _electron_dist_ok(PROJECT_ROOT):\n+                    restored = _redownload_electron_dist(PROJECT_ROOT, env)\n+                if purged or restored:\n+                    print(\"  ⚠ Desktop build failed; refreshed the 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","@@ -5383,12 +5477,25 @@ def cmd_gui(args: argparse.Namespace):\n                 # trade-off we only make AFTER the canonical GitHub download has\n                 # failed, and we never override a user-pinned ELECTRON_MIRROR.\n                 print(\"  ⚠ Desktop build still failing; the Electron download from \"\n-                      \"GitHub looks blocked. Retrying once via a public mirror \"\n+                      \"GitHub looks blocked. Re-downloading via a public mirror \"\n                       \"(npmmirror.com)... (set ELECTRON_MIRROR to use another mirror)\")\n+                mirror = \"https://npmmirror.com/mirrors/electron/\"\n                 mirror_env = dict(env)\n-                mirror_env[\"ELECTRON_MIRROR\"] = \"https://npmmirror.com/mirrors/electron/\"\n-                _stop_desktop_processes_locking_build(desktop_dir)\n-                build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=mirror_env, check=False)\n+                mirror_env[\"ELECTRON_MIRROR\"] = mirror\n+                # electronDist is pinned (#38673), so `npm run pack` never\n+                # downloads Electron — the mirror only helps if it drives\n+                # electron's own downloader. Re-fetch the binary through the\n+                # mirror first; otherwise the retry just re-reads the same missing\n+                # dist and re-throws \"electronDist does not exist\" (#47266).\n+                have_dist = _electron_dist_ok(PROJECT_ROOT)\n+                if not have_dist:\n+                    have_dist = _redownload_electron_dist(PROJECT_ROOT, env, mirror=mirror)\n+                if have_dist:\n+                    _stop_desktop_processes_locking_build(desktop_dir)\n+                    build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=mirror_env, check=False)\n+                else:\n+                    print(\"  ✗ Could not re-download Electron from the mirror \"\n+                          \"(node_modules/electron/dist still missing)\")\n             if build_result.returncode != 0:\n                 print(\"✗ Desktop GUI build failed\")\n                 print(f\"  Run manually:  cd apps/desktop && npm run {build_script}\")"]},{"file":"scripts/install.ps1","hunks":["@@ -2161,6 +2161,66 @@ function Clear-ElectronBuildCache {\n     return $removed\n }\n \n+# True when node_modules\\electron\\dist holds a usable Electron binary.\n+# electron-builder reads the binary from build.electronDist\n+# (node_modules\\electron\\dist) since #38673, so this is the exact file whose\n+# absence makes a pack fail with \"The specified electronDist does not exist\". A\n+# dist dir that exists but is missing electron.exe (partial extraction / aborted\n+# postinstall) is NOT ok.\n+function Test-ElectronDist {\n+    param([string]$InstallDir)\n+    $distExe = Join-Path $InstallDir 'node_modules\\electron\\dist\\electron.exe'\n+    return (Test-Path -LiteralPath $distExe)\n+}\n+\n+# (Re)populate node_modules\\electron\\dist via electron's own downloader.\n+#\n+# Since #38673 the desktop build pins build.electronDist to\n+# node_modules\\electron\\dist, so electron-builder reads the Electron binary\n+# straight from there and never downloads it during `npm run pack`. That dist\n+# tree is produced by the electron package's postinstall (install.js) during\n+# `npm ci`. When that download is blocked/throttled (GitHub's release host is\n+# unreachable in some regions - #47266), dist is missing and re-running pack only\n+# re-throws \"The specified electronDist does not exist\". The mirror fallback\n+# therefore has to drive THIS downloader, not another pack.\n+#\n+# No-op (returns $true) when the dist binary is already present. Otherwise drops a\n+# partial dist + version marker (electron's install.js short-circuits when\n+# path.txt already matches) and runs the downloader once, optionally via a\n+# mirror. Best-effort: never throws. Returns $true iff the dist binary exists\n+# afterward.\n+function Restore-ElectronDist {\n+    param([string]$InstallDir, [string]$Mirror)\n+    if (Test-ElectronDist -InstallDir $InstallDir) { return $true }\n+\n+    $electronDir = Join-Path $InstallDir 'node_modules\\electron'\n+    $distExe = Join-Path $electronDir 'dist\\electron.exe'\n+    $installer = Join-Path $electronDir 'install.js'\n+    if (-not (Test-Path -LiteralPath $installer)) { return $false }\n+    $node = Get-Command node -ErrorAction SilentlyContinue\n+    if (-not $node) { return $false }\n+\n+    $distDir = Join-Path $electronDir 'dist'\n+    if (Test-Path -LiteralPath $distDir) {\n+        Remove-Item -LiteralPath $distDir -Recurse -Force -ErrorAction SilentlyContinue\n+    }\n+    Remove-Item -LiteralPath (Join-Path $electronDir 'path.txt') -Force -ErrorAction SilentlyContinue\n+\n+    $prevMirror = $env:ELECTRON_MIRROR\n+    if ($Mirror) { $env:ELECTRON_MIRROR = $Mirror }\n+    try {\n+        # Out-Host so the downloader's progress shows on the console WITHOUT\n+        # leaking into this function's return value (PowerShell returns every\n+        # object left on the output stream, so a bare pipe here would make the\n+        # boolean below ambiguous).\n+        & $node.Source $installer 2>&1 | ForEach-Object { \"$_\" } | Out-Host\n+    } catch {\n+    } finally {\n+        $env:ELECTRON_MIRROR = $prevMirror\n+    }\n+    return (Test-Path -LiteralPath $distExe)\n+}\n+\n function Install-Desktop {\n     # Build apps/desktop into a launchable Hermes.exe. Only called from\n     # Stage-Desktop, which is itself only included in the manifest when","@@ -2310,8 +2370,19 @@ function Install-Desktop {\n             # once; @electron/get re-downloads with its own SHASUM check. Without\n             # this a corrupt download hard-fails the whole installer.\n             $purged = @(Clear-ElectronBuildCache -DesktopDir $desktopDir)\n-            if ($purged.Count -gt 0) {\n-                Write-Warn \"Desktop build failed - cleared cached Electron download, retrying once:\"\n+            # electronDist is pinned to node_modules\\electron\\dist (#38673):\n+            # electron-builder reads the Electron binary from there and `pack`\n+            # never downloads it, so purging the cache + re-running pack can't by\n+            # itself repopulate a missing/partial dist. When the dist is actually\n+            # gone, re-run electron's own downloader so the retry has a binary to\n+            # read. Gated on the dist check so an unrelated build failure\n+            # (tsc/vite) doesn't trigger a pointless ~200MB refetch.\n+            $restored = $false\n+            if (-not (Test-ElectronDist -InstallDir $InstallDir)) {\n+                $restored = Restore-ElectronDist -InstallDir $InstallDir\n+            }\n+            if ($purged.Count -gt 0 -or $restored) {\n+                Write-Warn \"Desktop build failed - refreshed the Electron download, retrying once:\"\n                 foreach ($p in $purged) { Write-Info \"  - $p\" }\n                 & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n                 $code = $LASTEXITCODE","@@ -2326,14 +2397,23 @@ function Install-Desktop {\n         # trade-off we only make AFTER the canonical GitHub download has failed,\n         # and we never override a user-pinned ELECTRON_MIRROR.\n         if ($code -ne 0 -and -not $env:ELECTRON_MIRROR) {\n-            $prevMirror = $env:ELECTRON_MIRROR\n-            $env:ELECTRON_MIRROR = \"https://npmmirror.com/mirrors/electron/\"\n+            $mirror = \"https://npmmirror.com/mirrors/electron/\"\n             Write-Warn \"Desktop build still failing - the Electron download from GitHub looks blocked.\"\n-            Write-Warn \"Retrying once via a public Electron mirror ($($env:ELECTRON_MIRROR)):\"\n+            Write-Warn \"Re-downloading Electron via a public mirror ($mirror), then rebuilding:\"\n             Write-Info \"  (set ELECTRON_MIRROR yourself to use a different/trusted mirror)\"\n-            & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n-            $code = $LASTEXITCODE\n-            $env:ELECTRON_MIRROR = $prevMirror\n+            # electronDist is pinned (#38673), so `npm run pack` never downloads\n+            # Electron - the mirror only helps if it drives electron's own\n+            # downloader. Re-fetch the binary through the mirror first; otherwise\n+            # the retry just re-reads the same missing dist and re-throws\n+            # \"The specified electronDist does not exist\" (#47266).\n+            $haveDist = Test-ElectronDist -InstallDir $InstallDir\n+            if (-not $haveDist) { $haveDist = Restore-ElectronDist -InstallDir $InstallDir -Mirror $mirror }\n+            if ($haveDist) {\n+                & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n+                $code = $LASTEXITCODE\n+            } else {\n+                Write-Warn \"Could not re-download Electron from the mirror (node_modules\\electron\\dist still missing)\"\n+            }\n         }\n         $ErrorActionPreference = $prevEAP\n         if ($code -ne 0) {"]},{"file":"scripts/install.sh","hunks":["@@ -2407,6 +2407,58 @@ _desktop_pack() {\n # failed, and we never override a user-pinned ELECTRON_MIRROR.\n DESKTOP_ELECTRON_FALLBACK_MIRROR=\"https://npmmirror.com/mirrors/electron/\"\n \n+# True (returns 0) when node_modules/electron/dist holds a usable Electron\n+# binary. electron-builder reads the binary from build.electronDist\n+# (node_modules/electron/dist) since #38673, so this is the exact file whose\n+# absence makes a pack fail with \"The specified electronDist does not exist\". A\n+# dist dir that exists but is missing the binary (partial extraction / aborted\n+# postinstall) is NOT ok. $1 = the workspace root holding node_modules.\n+_electron_dist_ok() {\n+    local install_dir=\"$1\"\n+    local electron_dir=\"$install_dir/node_modules/electron\"\n+    if [ \"$OS\" = \"macos\" ]; then\n+        [ -e \"$electron_dir/dist/Electron.app/Contents/MacOS/Electron\" ]\n+    else\n+        [ -e \"$electron_dir/dist/electron\" ]\n+    fi\n+}\n+\n+# (Re)populate node_modules/electron/dist via electron's own downloader.\n+#\n+# Since #38673 the desktop build pins build.electronDist to\n+# node_modules/electron/dist, so electron-builder reads the Electron binary\n+# straight from there and never downloads it during `npm run pack`. That dist\n+# tree is produced by the electron package's postinstall (install.js) during\n+# `npm ci`. When that download is blocked/throttled (GitHub's release host is\n+# unreachable in some regions - #47266), dist is missing and re-running pack only\n+# re-throws \"The specified electronDist does not exist\". The mirror fallback\n+# therefore has to drive THIS downloader, not another pack.\n+#\n+# No-op (returns 0) when the dist binary is already present. Otherwise drops a\n+# partial dist + version marker (electron's install.js short-circuits when\n+# path.txt already matches) and runs the downloader once. $1 = the workspace root\n+# holding node_modules; optional $2 = an ELECTRON_MIRROR base URL. Best-effort:\n+# returns 0 iff the dist binary exists afterward.\n+_restore_electron_dist() {\n+    local install_dir=\"$1\"\n+    local mirror=\"${2:-}\"\n+    local electron_dir=\"$install_dir/node_modules/electron\"\n+    _electron_dist_ok \"$install_dir\" && return 0\n+\n+    [ -f \"$electron_dir/install.js\" ] || return 1\n+    command -v node >/dev/null 2>&1 || return 1\n+\n+    rm -rf \"$electron_dir/dist\" 2>/dev/null || true\n+    rm -f \"$electron_dir/path.txt\" 2>/dev/null || true\n+\n+    if [ -n \"$mirror\" ]; then\n+        ( cd \"$electron_dir\" && ELECTRON_MIRROR=\"$mirror\" node install.js ) || true\n+    else\n+        ( cd \"$electron_dir\" && node install.js ) || true\n+    fi\n+    _electron_dist_ok \"$install_dir\"\n+}\n+\n # Build apps/desktop into a launchable native app. Mirrors install.ps1's\n # Install-Desktop: a root-level npm install so the apps/* workspace resolves\n # the desktop's own deps (Electron ~150MB), then `npm run pack`","@@ -2479,8 +2531,19 @@ install_desktop() {\n         # (b) Corrupt cached Electron zip is the most common self-healable cause.\n         local purged\n         purged=\"$(clear_electron_build_cache \"$desktop_dir\")\"\n-        if [ -n \"$purged\" ]; then\n-            log_warn \"Desktop build failed; cleared cached Electron download and retrying once...\"\n+        # electronDist is pinned to node_modules/electron/dist (#38673):\n+        # electron-builder reads the binary from there and `pack` never downloads\n+        # it, so purging the cache + re-running pack can't by itself repopulate a\n+        # missing/partial dist. When the dist is actually gone, re-run electron's\n+        # own downloader so the retry has a binary to read. Gated on the dist\n+        # check so an unrelated build failure (tsc/vite) doesn't trigger a\n+        # pointless ~200MB refetch.\n+        local restored=false\n+        if ! _electron_dist_ok \"$INSTALL_DIR\"; then\n+            if _restore_electron_dist \"$INSTALL_DIR\"; then restored=true; fi\n+        fi\n+        if [ -n \"$purged\" ] || [ \"$restored\" = true ]; then\n+            log_warn \"Desktop build failed; refreshed the Electron download and retrying once...\"\n             if _desktop_pack \"$desktop_dir\"; then\n                 pack_ok=true\n             fi","@@ -2488,14 +2551,26 @@ install_desktop() {\n     fi\n \n     # (c) Still failing and the user hasn't pinned their own mirror: the GitHub\n-    #     release host is likely blocked/throttled. Retry once via a public\n-    #     Electron mirror (@electron/get still SHASUM-verifies the download).\n+    #     release host is likely blocked/throttled. Re-download the Electron\n+    #     binary via a public mirror, then retry. The mirror MUST drive\n+    #     electron's own downloader — `npm run pack` reads the pinned electronDist\n+    #     and never downloads, so a mirror passed only to pack is a no-op (#47266).\n     if [ \"$pack_ok\" = false ] && [ -z \"${ELECTRON_MIRROR:-}\" ]; then\n         log_warn \"Desktop build still failing — the Electron download from GitHub looks blocked.\"\n-        log_warn \"Retrying once via a public Electron mirror ($DESKTOP_ELECTRON_FALLBACK_MIRROR)...\"\n+        log_warn \"Re-downloading Electron via a public mirror ($DESKTOP_ELECTRON_FALLBACK_MIRROR), then rebuilding...\"\n         log_warn \"  (set ELECTRON_MIRROR yourself to use a different/trusted mirror)\"\n-        if _desktop_pack \"$desktop_dir\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n-            pack_ok=true\n+        local have_dist=false\n+        if _electron_dist_ok \"$INSTALL_DIR\"; then\n+            have_dist=true\n+        elif _restore_electron_dist \"$INSTALL_DIR\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n+            have_dist=true\n+        fi\n+        if [ \"$have_dist\" = true ]; then\n+            if _desktop_pack \"$desktop_dir\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n+                pack_ok=true\n+            fi\n+        else\n+            log_warn \"Could not re-download Electron from the mirror (node_modules/electron/dist still missing)\"\n         fi\n     fi\n "]},{"file":"tests/hermes_cli/test_gui_command.py","hunks":["@@ -498,9 +498,10 @@ def test_gui_retries_pack_once_after_purging_build_cache(tmp_path, monkeypatch):\n     assert mock_run.call_args_list[2].args[0] == [str(packaged_exe)]\n \n \n-def test_gui_falls_back_to_mirror_when_purge_finds_nothing(tmp_path, monkeypatch, capsys):\n-    \"\"\"Purge clears nothing (not a cache problem) → fall back to an Electron\n-    mirror once before failing, so a GitHub-blocked download self-heals.\"\"\"\n+def test_gui_redownloads_electron_via_mirror_then_repacks(tmp_path, monkeypatch, capsys):\n+    \"\"\"Purge clears nothing and the pinned electronDist (#38673) is missing →\n+    the mirror fallback must drive electron's own downloader (NOT another pack,\n+    which never downloads Electron) and only then retry pack (#47266).\"\"\"\n     root = _make_desktop_tree(tmp_path)\n     monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n     _make_packaged_executable(root, monkeypatch, platform=\"linux\")","@@ -512,21 +513,59 @@ def test_gui_falls_back_to_mirror_when_purge_finds_nothing(tmp_path, monkeypatch\n     with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/npm\"), \\\n          patch(\"hermes_cli.main._run_npm_install_deterministic\", return_value=install_ok), \\\n          patch(\"hermes_cli.main._desktop_macos_relaunchable_fixup\"), \\\n-         patch(\"hermes_cli.main._purge_electron_build_cache\", return_value=[]) as mock_purge, \\\n+         patch(\"hermes_cli.main._purge_electron_build_cache\", return_value=[]), \\\n+         patch(\"hermes_cli.main._electron_dist_ok\", return_value=False), \\\n+         patch(\"hermes_cli.main._redownload_electron_dist\", side_effect=[False, True]) as mock_dl, \\\n          patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_fail, pack_fail]) as mock_run, \\\n          pytest.raises(SystemExit) as exc:\n         cli_main.cmd_gui(_ns())\n \n     assert exc.value.code == 1\n-    mock_purge.assert_called_once()\n-    # pack(fail) → purge(nothing) → pack via mirror(fail) = 2 subprocess.run calls\n+    # initial pack + mirror pack = 2 npm calls. The first-retry pack is skipped\n+    # because the canonical-source re-download (no mirror) failed, so there was\n+    # never a binary to build against.\n     assert mock_run.call_count == 2\n-    # The retry runs the same build but with ELECTRON_MIRROR injected.\n+    # First re-download attempt is canonical (no mirror); the second drives the\n+    # public mirror.\n+    assert mock_dl.call_args_list[0].kwargs.get(\"mirror\") is None\n+    assert mock_dl.call_args_list[1].kwargs[\"mirror\"]\n+    # Only the mirror-driven pack carries ELECTRON_MIRROR.\n     assert \"ELECTRON_MIRROR\" not in (mock_run.call_args_list[0].kwargs.get(\"env\") or {})\n     assert mock_run.call_args_list[1].kwargs[\"env\"][\"ELECTRON_MIRROR\"]\n     assert \"Desktop GUI build failed\" in capsys.readouterr().out\n \n \n+def test_gui_skips_pack_when_electron_redownload_unrecoverable(tmp_path, monkeypatch, capsys):\n+    \"\"\"When the Electron binary can't be fetched at all (mirror also blocked),\n+    skip the pointless final pack — it would just re-throw the same missing\n+    electronDist — and fail with a clear message instead.\"\"\"\n+    root = _make_desktop_tree(tmp_path)\n+    monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n+    _make_packaged_executable(root, monkeypatch, platform=\"linux\")\n+    monkeypatch.delenv(\"ELECTRON_MIRROR\", raising=False)\n+\n+    install_ok = subprocess.CompletedProcess([\"npm\", \"ci\"], 0)\n+    pack_fail = subprocess.CompletedProcess([\"npm\", \"run\", \"pack\"], 1)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/npm\"), \\\n+         patch(\"hermes_cli.main._run_npm_install_deterministic\", return_value=install_ok), \\\n+         patch(\"hermes_cli.main._desktop_macos_relaunchable_fixup\"), \\\n+         patch(\"hermes_cli.main._purge_electron_build_cache\", return_value=[]), \\\n+         patch(\"hermes_cli.main._electron_dist_ok\", return_value=False), \\\n+         patch(\"hermes_cli.main._redownload_electron_dist\", return_value=False), \\\n+         patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_fail]) as mock_run, \\\n+         pytest.raises(SystemExit) as exc:\n+        cli_main.cmd_gui(_ns())\n+\n+    assert exc.value.code == 1\n+    # Only the initial pack ran; both retries were skipped because no binary\n+    # could be produced.\n+    assert mock_run.call_count == 1\n+    out = capsys.readouterr().out\n+    assert \"Could not re-download Electron from the mirror\" in out\n+    assert \"Desktop GUI build failed\" in out\n+\n+\n def test_gui_does_not_override_user_electron_mirror(tmp_path, monkeypatch, capsys):\n     \"\"\"A user-pinned ELECTRON_MIRROR is respected: no extra mirror fallback\n     attempt (and we never swap in our default mirror).\"\"\"","@@ -553,6 +592,108 @@ def test_gui_does_not_override_user_electron_mirror(tmp_path, monkeypatch, capsy\n     assert \"Desktop GUI build failed\" in capsys.readouterr().out\n \n \n+# ── electronDist (re)download helper tests (#47266) ───────────────────\n+\n+\n[redacted:email](\n+    \"platform,rel\",\n+    [\n+        (\"linux\", \"dist/electron\"),\n+        (\"win32\", \"dist/electron.exe\"),\n+        (\"darwin\", \"dist/Electron.app/Contents/MacOS/Electron\"),\n+    ],\n+)\n+def test_electron_dist_ok_per_platform(tmp_path, monkeypatch, platform, rel):\n+    monkeypatch.setattr(cli_main.sys, \"platform\", platform)\n+    electron = tmp_path / \"node_modules\" / \"electron\"\n+    # A dist dir that exists but lacks the binary is NOT ok (partial extraction).\n+    (electron / \"dist\").mkdir(parents=True)\n+    assert cli_main._electron_dist_ok(tmp_path) is False\n+\n+    binp = electron / rel\n+    binp.parent.mkdir(parents=True, exist_ok=True)\n+    binp.write_text(\"\", encoding=\"utf-8\")\n+    assert cli_main._electron_dist_ok(tmp_path) is True\n+\n+\n+def test_redownload_electron_dist_noop_when_present(tmp_path, monkeypatch):\n+    \"\"\"Already-healthy dist → no download, so an unrelated build failure can't\n+    trigger a needless ~200 MB refetch.\"\"\"\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+    binp = tmp_path / \"node_modules\" / \"electron\" / \"dist\" / \"electron\"\n+    binp.parent.mkdir(parents=True)\n+    binp.write_text(\"\", encoding=\"utf-8\")\n+\n+    with patch(\"hermes_cli.main.subprocess.run\") as mock_run:\n+        assert cli_main._redownload_electron_dist(tmp_path, {}) is True\n+    mock_run.assert_not_called()\n+\n+\n+def test_redownload_electron_dist_missing_installer(tmp_path, monkeypatch):\n+    \"\"\"No electron/install.js (deps never installed) → nothing to run.\"\"\"\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+    (tmp_path / \"node_modules\" / \"electron\").mkdir(parents=True)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/node\"), \\\n+         patch(\"hermes_cli.main.subprocess.run\") as mock_run:\n+        assert cli_main._redownload_electron_dist(tmp_path, {}) is False\n+    mock_run.assert_not_called()\n+\n+\n+def test_redownload_electron_dist_runs_installer_with_mirror(tmp_path, monkeypatch):\n+    \"\"\"Missing dist → wipe any partial dist + version marker, run electron's own\n+    install.js with ELECTRON_MIRROR injected, and report success on the binary.\"\"\"\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+    electron = tmp_path / \"node_modules\" / \"electron\"\n+    electron.mkdir(parents=True)\n+    (electron / \"install.js\").write_text(\"// stub\", encoding=\"utf-8\")\n+    # A stale partial dist + version marker that MUST be cleared first, otherwise\n+    # electron's install.js short-circuits on path.txt and never re-downloads.\n+    (electron / \"dist\").mkdir()\n+    (electron / \"dist\" / \"leftover\").write_text(\"junk\", encoding=\"utf-8\")\n+    (electron / \"path.txt\").write_text(\"electron\", encoding=\"utf-8\")\n+\n+    captured = {}\n+\n+    def fake_run(cmd, **kwargs):\n+        captured[\"cmd\"] = cmd\n+        captured[\"env\"] = kwargs.get(\"env\")\n+        captured[\"cwd\"] = kwargs.get(\"cwd\")\n+        # simulate electron's install.js producing the dist binary\n+        binp = electron / \"dist\" / \"electron\"\n+        binp.parent.mkdir(parents=True, exist_ok=True)\n+        binp.write_text(\"\", encoding=\"utf-8\")\n+        return subprocess.CompletedProcess(cmd, 0)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/node\"), \\\n+         patch(\"hermes_cli.main.subprocess.run\", side_effect=fake_run):\n+        ok = cli_main._redownload_electron_dist(\n+            tmp_path, {\"PATH\": \"/x\"}, mirror=\"https://mirror.example/electron/\"\n+        )\n+\n+    assert ok is True\n+    assert captured[\"cmd\"] == [\"/usr/bin/node\", str(electron / \"install.js\")]\n+    assert captured[\"cwd\"] == str(electron)\n+    assert captured[\"env\"][\"ELECTRON_MIRROR\"] == \"https://mirror.example/electron/\"\n+    # The partial dir + marker were dropped before the re-download.\n+    assert not (electron / \"dist\" / \"leftover\").exists()\n+    assert not (electron / \"path.txt\").exists()\n+\n+\n+def test_redownload_electron_dist_returns_false_when_download_fails(tmp_path, monkeypatch):\n+    \"\"\"install.js ran but produced no binary (still blocked) → False, so the\n+    caller skips a doomed pack.\"\"\"\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+    electron = tmp_path / \"node_modules\" / \"electron\"\n+    electron.mkdir(parents=True)\n+    (electron / \"install.js\").write_text(\"// stub\", encoding=\"utf-8\")\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/node\"), \\\n+         patch(\"hermes_cli.main.subprocess.run\",\n+               return_value=subprocess.CompletedProcess([\"node\"], 1)):\n+        assert cli_main._redownload_electron_dist(tmp_path, {}) is False\n+\n+\n class _FakeProc:\n     \"\"\"Minimal psutil.Process stand-in for the lock-breaker tests.\"\"\"\n \n"]}]},{"kind":"pr","number":48081,"title":"fix(desktop): resolve electronDist to the actual electron install location","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/package.json","apps/desktop/scripts/patch-electron-builder-mac-binary.cjs","hermes_cli/main.py","scripts/install.ps1","scripts/install.sh","scripts/release.py","tests/hermes_cli/test_gui_command.py","tests/test_desktop_electron_pin.py"],"fix_hunks":[{"file":"apps/desktop/package.json","hunks":["@@ -135,7 +135,7 @@\n   },\n   \"build\": {\n     \"electronVersion\": \"40.10.2\",\n-    \"electronDist\": \"../../node_modules/electron/dist\",\n+    \"electronDist\": \"node_modules/electron/dist\",\n     \"appId\": \"com.nousresearch.hermes\",\n     \"productName\": \"Hermes\",\n     \"executableName\": \"Hermes\","]},{"file":"apps/desktop/scripts/patch-electron-builder-mac-binary.cjs","hunks":["@@ -24,6 +24,11 @@ const replacement = `    // ${marker}: electron-builder 26.8.x can sometimes cop\n     if (!fs.existsSync(bundledElectronBinary)) {\n         const candidates = [\n             path.join(packager.info.framework.distMacOsAppName, \"Contents\", \"MacOS\", electronBranding.productName),\n+            // npm may nest the workspace-only electron devDep under\n+            // apps/desktop/node_modules (process.cwd() during pack), or hoist\n+            // it to the repo root. Try the workspace-local install first, then\n+            // the root hoist, so the fallback works under either layout.\n+            path.join(process.cwd(), \"node_modules\", \"electron\", \"dist\", \"Electron.app\", \"Contents\", \"MacOS\", electronBranding.productName),\n             path.join(process.cwd(), \"..\", \"..\", \"node_modules\", \"electron\", \"dist\", \"Electron.app\", \"Contents\", \"MacOS\", electronBranding.productName),\n         ];\n         const sourceBinary = candidates.find(candidate => fs.existsSync(candidate));"]},{"file":"hermes_cli/main.py","hunks":["@@ -5110,16 +5110,33 @@ def _purge_electron_build_cache(desktop_dir: Path) -> list[Path]:\n     return removed\n \n \n+def _electron_dir(project_root: Path) -> Path:\n+    \"\"\"Return the Electron package directory the desktop workspace installs.\n+\n+    npm may keep workspace-only dev dependencies under\n+    ``apps/desktop/node_modules`` instead of hoisting them to the repo root.\n+    Which layout you get depends on the npm version and what else is installed,\n+    so a build path that assumes one or the other breaks intermittently across\n+    machines. ``apps/desktop/package.json`` points electron-builder's\n+    ``electronDist`` at ``node_modules/electron/dist`` relative to the desktop\n+    project, so prefer the workspace-local package and fall back to the root\n+    hoist when that's where npm landed it.\n+    \"\"\"\n+    desktop_local = project_root / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\"\n+    if desktop_local.exists():\n+        return desktop_local\n+    return project_root / \"node_modules\" / \"electron\"\n+\n+\n def _electron_dist_binary(project_root: Path) -> Path:\n-    \"\"\"Return the path to the Electron main binary inside ``node_modules``.\n+    \"\"\"Return the path to the Electron main binary inside the installed package.\n \n-    electron-builder reads the binary from ``build.electronDist``\n-    (``node_modules/electron/dist``) since #38673, so this is the exact file\n-    whose absence makes a pack fail with \"The specified electronDist does not\n-    exist\". The basename differs per OS (the platform Electron is named for the\n-    host the build runs on).\n+    electron-builder reads the binary from ``build.electronDist`` since #38673,\n+    so this is the exact file whose absence makes a pack fail with \"The\n+    specified electronDist does not exist\". The basename differs per OS (the\n+    platform Electron is named for the host the build runs on).\n     \"\"\"\n-    dist = project_root / \"node_modules\" / \"electron\" / \"dist\"\n+    dist = _electron_dir(project_root) / \"dist\"\n     if sys.platform == \"darwin\":\n         return dist / \"Electron.app\" / \"Contents\" / \"MacOS\" / \"Electron\"\n     if sys.platform == \"win32\":","@@ -5169,7 +5186,7 @@ def _redownload_electron_dist(\n     if _electron_dist_ok(project_root):\n         return True\n \n-    electron_dir = project_root / \"node_modules\" / \"electron\"\n+    electron_dir = _electron_dir(project_root)\n     installer = electron_dir / \"install.js\"\n     if not installer.is_file():\n         return False","@@ -5387,8 +5404,8 @@ def cmd_gui(args: argparse.Namespace):\n                 print(\"  Pre-build first:  cd apps/desktop && npm run build\")\n                 print(\"  Or drop --skip-build to install dependencies and build automatically.\")\n                 sys.exit(1)\n-            if not (PROJECT_ROOT / \"node_modules\" / \"electron\" / \"package.json\").exists():\n-                print(\"✗ --skip-build --source requires existing workspace dependencies.\")\n+            if not (_electron_dir(PROJECT_ROOT) / \"package.json\").exists():\n+                print(\"✗ --skip-build --source requires existing desktop workspace dependencies.\")\n                 print(f\"  Install first:  cd {PROJECT_ROOT} && npm ci\")\n                 print(\"  Or drop --skip-build to install dependencies and build automatically.\")\n                 sys.exit(1)"]},{"file":"scripts/install.ps1","hunks":["@@ -2161,28 +2161,41 @@ function Clear-ElectronBuildCache {\n     return $removed\n }\n \n-# True when node_modules\\electron\\dist holds a usable Electron binary.\n-# electron-builder reads the binary from build.electronDist\n-# (node_modules\\electron\\dist) since #38673, so this is the exact file whose\n-# absence makes a pack fail with \"The specified electronDist does not exist\". A\n-# dist dir that exists but is missing electron.exe (partial extraction / aborted\n-# postinstall) is NOT ok.\n+# Return the Electron package directory the desktop workspace installs. npm may\n+# nest workspace-only dev dependencies under apps\\desktop\\node_modules instead\n+# of hoisting them to the repo root; which layout you get depends on the npm\n+# version and what else is installed. apps\\desktop\\package.json points\n+# electron-builder's electronDist there, so prefer the workspace-local package\n+# and fall back to the root hoist.\n+function Get-ElectronDir {\n+    param([string]$InstallDir)\n+    $desktopLocal = Join-Path $InstallDir 'apps\\desktop\\node_modules\\electron'\n+    if (Test-Path -LiteralPath $desktopLocal) { return $desktopLocal }\n+    return (Join-Path $InstallDir 'node_modules\\electron')\n+}\n+\n+# True when the desktop workspace electronDist holds a usable Electron binary.\n+# electron-builder reads the binary from build.electronDist since #38673, so\n+# this is the exact file whose absence makes a pack fail with \"The specified\n+# electronDist does not exist\". A dist dir that exists but is missing\n+# electron.exe (partial extraction / aborted postinstall) is NOT ok.\n function Test-ElectronDist {\n     param([string]$InstallDir)\n-    $distExe = Join-Path $InstallDir 'node_modules\\electron\\dist\\electron.exe'\n+    $electronDir = Get-ElectronDir -InstallDir $InstallDir\n+    $distExe = Join-Path $electronDir 'dist\\electron.exe'\n     return (Test-Path -LiteralPath $distExe)\n }\n \n-# (Re)populate node_modules\\electron\\dist via electron's own downloader.\n+# (Re)populate the desktop Electron dist via electron's own downloader.\n #\n-# Since #38673 the desktop build pins build.electronDist to\n-# node_modules\\electron\\dist, so electron-builder reads the Electron binary\n-# straight from there and never downloads it during `npm run pack`. That dist\n-# tree is produced by the electron package's postinstall (install.js) during\n-# `npm ci`. When that download is blocked/throttled (GitHub's release host is\n-# unreachable in some regions - #47266), dist is missing and re-running pack only\n-# re-throws \"The specified electronDist does not exist\". The mirror fallback\n-# therefore has to drive THIS downloader, not another pack.\n+# Since #38673 the desktop build pins build.electronDist, so electron-builder\n+# reads the Electron binary straight from there and never downloads it during\n+# `npm run pack`. That dist tree is produced by the electron package's\n+# postinstall (install.js) during `npm ci`. When that download is\n+# blocked/throttled (GitHub's release host is unreachable in some regions -\n+# #47266), dist is missing and re-running pack only re-throws \"The specified\n+# electronDist does not exist\". The mirror fallback therefore has to drive THIS\n+# downloader, not another pack.\n #\n # No-op (returns $true) when the dist binary is already present. Otherwise drops a\n # partial dist + version marker (electron's install.js short-circuits when","@@ -2193,7 +2206,7 @@ function Restore-ElectronDist {\n     param([string]$InstallDir, [string]$Mirror)\n     if (Test-ElectronDist -InstallDir $InstallDir) { return $true }\n \n-    $electronDir = Join-Path $InstallDir 'node_modules\\electron'\n+    $electronDir = Get-ElectronDir -InstallDir $InstallDir\n     $distExe = Join-Path $electronDir 'dist\\electron.exe'\n     $installer = Join-Path $electronDir 'install.js'\n     if (-not (Test-Path -LiteralPath $installer)) { return $false }"]},{"file":"scripts/install.sh","hunks":["@@ -2407,15 +2407,31 @@ _desktop_pack() {\n # failed, and we never override a user-pinned ELECTRON_MIRROR.\n DESKTOP_ELECTRON_FALLBACK_MIRROR=\"https://npmmirror.com/mirrors/electron/\"\n \n-# True (returns 0) when node_modules/electron/dist holds a usable Electron\n-# binary. electron-builder reads the binary from build.electronDist\n-# (node_modules/electron/dist) since #38673, so this is the exact file whose\n-# absence makes a pack fail with \"The specified electronDist does not exist\". A\n-# dist dir that exists but is missing the binary (partial extraction / aborted\n-# postinstall) is NOT ok. $1 = the workspace root holding node_modules.\n+# Return the Electron package directory the desktop workspace installs. npm may\n+# nest workspace-only dev dependencies under apps/desktop/node_modules instead\n+# of hoisting them to the repo root; which layout you get depends on the npm\n+# version and what else is installed. apps/desktop/package.json points\n+# electron-builder's electronDist there, so prefer the workspace-local package\n+# and fall back to the root hoist. $1 = the workspace root holding node_modules.\n+_electron_dir() {\n+    local install_dir=\"$1\"\n+    if [ -d \"$install_dir/apps/desktop/node_modules/electron\" ]; then\n+        printf '%s\\n' \"$install_dir/apps/desktop/node_modules/electron\"\n+    else\n+        printf '%s\\n' \"$install_dir/node_modules/electron\"\n+    fi\n+}\n+\n+# True (returns 0) when the desktop workspace electronDist holds a usable\n+# Electron binary. electron-builder reads the binary from build.electronDist\n+# since #38673, so this is the exact file whose absence makes a pack fail with\n+# \"The specified electronDist does not exist\". A dist dir that exists but is\n+# missing the binary (partial extraction / aborted postinstall) is NOT ok.\n+# $1 = the workspace root holding node_modules.\n _electron_dist_ok() {\n     local install_dir=\"$1\"\n-    local electron_dir=\"$install_dir/node_modules/electron\"\n+    local electron_dir\n+    electron_dir=\"$(_electron_dir \"$install_dir\")\"\n     if [ \"$OS\" = \"macos\" ]; then\n         [ -e \"$electron_dir/dist/Electron.app/Contents/MacOS/Electron\" ]\n     else","@@ -2423,16 +2439,16 @@ _electron_dist_ok() {\n     fi\n }\n \n-# (Re)populate node_modules/electron/dist via electron's own downloader.\n+# (Re)populate the desktop Electron dist via electron's own downloader.\n #\n-# Since #38673 the desktop build pins build.electronDist to\n-# node_modules/electron/dist, so electron-builder reads the Electron binary\n-# straight from there and never downloads it during `npm run pack`. That dist\n-# tree is produced by the electron package's postinstall (install.js) during\n-# `npm ci`. When that download is blocked/throttled (GitHub's release host is\n-# unreachable in some regions - #47266), dist is missing and re-running pack only\n-# re-throws \"The specified electronDist does not exist\". The mirror fallback\n-# therefore has to drive THIS downloader, not another pack.\n+# Since #38673 the desktop build pins build.electronDist, so electron-builder\n+# reads the Electron binary straight from there and never downloads it during\n+# `npm run pack`. That dist tree is produced by the electron package's\n+# postinstall (install.js) during `npm ci`. When that download is\n+# blocked/throttled (GitHub's release host is unreachable in some regions -\n+# #47266), dist is missing and re-running pack only re-throws \"The specified\n+# electronDist does not exist\". The mirror fallback therefore has to drive THIS\n+# downloader, not another pack.\n #\n # No-op (returns 0) when the dist binary is already present. Otherwise drops a\n # partial dist + version marker (electron's install.js short-circuits when","@@ -2442,7 +2458,8 @@ _electron_dist_ok() {\n _restore_electron_dist() {\n     local install_dir=\"$1\"\n     local mirror=\"${2:-}\"\n-    local electron_dir=\"$install_dir/node_modules/electron\"\n+    local electron_dir\n+    electron_dir=\"$(_electron_dir \"$install_dir\")\"\n     _electron_dist_ok \"$install_dir\" && return 0\n \n     [ -f \"$electron_dir/install.js\" ] || return 1"]},{"file":"scripts/release.py","hunks":["@@ -45,6 +45,9 @@\n \n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n+    \"[redacted:email]\": \"sitkarev\",\n+    \"[redacted:email]\": \"omegazheng\",\n+    \"[redacted:email]\": \"james47kjv\",\n     \"[redacted:email]\": \"1960697431\",\n     \"[redacted:email]\": \"definitelynotguru\",\n     \"[redacted:email]\": \"ChasLui\","]},{"file":"tests/hermes_cli/test_gui_command.py","hunks":["@@ -616,6 +616,33 @@ def test_electron_dist_ok_per_platform(tmp_path, monkeypatch, platform, rel):\n     assert cli_main._electron_dist_ok(tmp_path) is True\n \n \n+def test_electron_dir_prefers_workspace_local_package(tmp_path):\n+    \"\"\"npm may nest electron under apps/desktop; resolve there over the root hoist.\"\"\"\n+    root_electron = tmp_path / \"node_modules\" / \"electron\"\n+    local_electron = tmp_path / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\"\n+    root_electron.mkdir(parents=True)\n+    local_electron.mkdir(parents=True)\n+\n+    assert cli_main._electron_dir(tmp_path) == local_electron\n+\n+\n+def test_electron_dir_falls_back_to_root_hoist(tmp_path):\n+    \"\"\"When npm hoists electron to the repo root, resolve there.\"\"\"\n+    root_electron = tmp_path / \"node_modules\" / \"electron\"\n+    root_electron.mkdir(parents=True)\n+\n+    assert cli_main._electron_dir(tmp_path) == root_electron\n+\n+\n+def test_electron_dist_ok_finds_workspace_local_binary(tmp_path, monkeypatch):\n+    \"\"\"A nested apps/desktop electron with a valid binary counts as ok.\"\"\"\n+    monkeypatch.setattr(cli_main.sys, \"platform\", \"linux\")\n+    binp = tmp_path / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\" / \"dist\" / \"electron\"\n+    binp.parent.mkdir(parents=True)\n+    binp.write_text(\"\", encoding=\"utf-8\")\n+    assert cli_main._electron_dist_ok(tmp_path) is True\n+\n+\n def test_redownload_electron_dist_noop_when_present(tmp_path, monkeypatch):\n     \"\"\"Already-healthy dist → no download, so an unrelated build failure can't\n     trigger a needless ~200 MB refetch.\"\"\""]},{"file":"tests/test_desktop_electron_pin.py","hunks":["@@ -94,3 +94,42 @@ def test_lockfile_resolves_the_pinned_electron():\n         f\"but the pin is {spec!r}; run `npm install --package-lock-only` so \"\n         \"`npm ci` stays consistent.\"\n     )\n+\n+\n+def test_electron_dist_matches_lockfile_install_location():\n+    \"\"\"build.electronDist must point at where the lockfile installs Electron.\n+\n+    electron-builder copies the unpacked Electron from ``build.electronDist``\n+    (resolved relative to ``apps/desktop``). npm workspace hoisting is not\n+    deterministic across machines/npm versions: it may nest Electron under\n+    ``apps/desktop/node_modules/electron`` or hoist it to the repo root. If\n+    electronDist points at one location while the lockfile installs at the\n+    other, packaging fails with ``The specified electronDist does not exist`` —\n+    the \"Building desktop app\" failure reported after the June lockfile\n+    regeneration floated Electron and reshuffled the hoist. Lock the two\n+    together so a hoist change (root <-> nested) can't silently break the path\n+    again.\n+    \"\"\"\n+    if not ROOT_LOCK.is_file():\n+        pytest.skip(\"root package-lock.json not present\")\n+    electron_dist = _desktop_pkg().get(\"build\", {}).get(\"electronDist\")\n+    assert electron_dist, \"build.electronDist is missing\"\n+\n+    lock = json.loads(ROOT_LOCK.read_text(encoding=\"utf-8\"))\n+    electron_paths = [\n+        path\n+        for path in lock.get(\"packages\", {})\n+        if path.endswith(\"node_modules/electron\")\n+    ]\n+    assert electron_paths, \"no electron entry found in package-lock.json\"\n+\n+    desktop_dir = REPO_ROOT / \"apps\" / \"desktop\"\n+    # electronDist is resolved relative to the apps/desktop project dir.\n+    configured = (desktop_dir / electron_dist).resolve()\n+    # Where the lockfile actually places Electron's unpacked dist.\n+    installed = {(REPO_ROOT / p / \"dist\").resolve() for p in electron_paths}\n+    assert configured in installed, (\n+        f\"build.electronDist={electron_dist!r} resolves to {configured}, but the \"\n+        f\"lockfile installs Electron at {sorted(str(p) for p in installed)}. \"\n+        \"electron-builder will fail with 'electronDist does not exist'.\"\n+    )\n"]}]},{"kind":"pr","number":48091,"title":"fix(desktop): resolve electronDist dynamically + self-heal blocked installs (supersedes #48081/#48082)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/package.json","apps/desktop/scripts/run-electron-builder.cjs","hermes_cli/main.py","scripts/install.ps1","scripts/install.sh","tests/hermes_cli/test_gui_command.py","tests/test_desktop_electron_pin.py"],"fix_hunks":[{"file":"apps/desktop/package.json","hunks":["@@ -21,7 +21,7 @@\n     \"build\": \"node scripts/assert-root-install.cjs && node scripts/write-build-stamp.cjs && node scripts/stage-native-deps.cjs && tsc -b && vite build && npm run postbuild\",\n     \"postbuild\": \"node scripts/assert-dist-built.cjs\",\n     \"prebuilder\": \"node scripts/patch-electron-builder-mac-binary.cjs\",\n-    \"builder\": \"cross-env NODE_OPTIONS=--max-old-space-size=16384 electron-builder\",\n+    \"builder\": \"cross-env NODE_OPTIONS=--max-old-space-size=16384 node scripts/run-electron-builder.cjs\",\n     \"pack\": \"npm run build && npm run builder -- --dir\",\n     \"dist\": \"npm run build && npm run builder\",\n     \"dist:mac\": \"npm run build && npm run builder -- --mac\",","@@ -135,7 +135,6 @@\n   },\n   \"build\": {\n     \"electronVersion\": \"40.10.2\",\n-    \"electronDist\": \"node_modules/electron/dist\",\n     \"appId\": \"com.nousresearch.hermes\",\n     \"productName\": \"Hermes\",\n     \"executableName\": \"Hermes\","]},{"file":"apps/desktop/scripts/run-electron-builder.cjs","hunks":["@@ -0,0 +1,57 @@\n+\"use strict\"\n+\n+// Resolve electronDist at runtime (#38673, #47917): electron-builder 26.8.x can\n+// re-unpack a broken Electron.app; reusing the installed dist dodges that.\n+// npm workspace hoisting is non-deterministic — require.resolve finds electron\n+// wherever it landed. Dist present → -c.electronDist=<abs>/dist; absent → let\n+// electron-builder fetch via @electron/get (electronVersion + ELECTRON_MIRROR).\n+\n+const fs = require(\"node:fs\")\n+const path = require(\"node:path\")\n+const { spawnSync } = require(\"node:child_process\")\n+\n+function electronDistDir() {\n+  try {\n+    return path.join(path.dirname(require.resolve(\"electron/package.json\")), \"dist\")\n+  } catch {\n+    return null\n+  }\n+}\n+\n+function distBinary(dist) {\n+  if (process.platform === \"darwin\") {\n+    return path.join(dist, \"Electron.app\", \"Contents\", \"MacOS\", \"Electron\")\n+  }\n+  if (process.platform === \"win32\") {\n+    return path.join(dist, \"electron.exe\")\n+  }\n+  return path.join(dist, \"electron\")\n+}\n+\n+function electronBuilderCli() {\n+  const pkgJson = require.resolve(\"electron-builder/package.json\")\n+  const bin = require(pkgJson).bin\n+  const rel = typeof bin === \"string\" ? bin : bin[\"electron-builder\"]\n+  return path.join(path.dirname(pkgJson), rel)\n+}\n+\n+const dist = electronDistDir()\n+const args = []\n+if (dist && fs.existsSync(distBinary(dist))) {\n+  args.push(`-c.electronDist=${dist}`)\n+} else {\n+  console.warn(\n+    \"[run-electron-builder] no local electron dist; electron-builder will fetch \" +\n+      \"via @electron/get (electronVersion + ELECTRON_MIRROR).\"\n+  )\n+}\n+args.push(...process.argv.slice(2))\n+\n+const result = spawnSync(process.execPath, [electronBuilderCli(), ...args], {\n+  stdio: \"inherit\",\n+})\n+if (result.error) {\n+  console.error(`[run-electron-builder] spawn failed: ${result.error.message}`)\n+  process.exit(1)\n+}\n+process.exit(result.status == null ? 1 : result.status)"]},{"file":"hermes_cli/main.py","hunks":["@@ -5110,6 +5110,11 @@ def _purge_electron_build_cache(desktop_dir: Path) -> list[Path]:\n     return removed\n \n \n+# Last-resort Electron mirror after GitHub download fails (#47266). Only used\n+# when the user hasn't pinned ELECTRON_MIRROR.\n+_ELECTRON_FALLBACK_MIRROR = \"https://npmmirror.com/mirrors/electron/\"\n+\n+\n def _electron_dir(project_root: Path) -> Path:\n     \"\"\"Return the Electron package directory the desktop workspace installs.\n ","@@ -5158,31 +5163,23 @@ def _electron_dist_ok(project_root: Path) -> bool:\n         return False\n \n \n+def _electron_pkg_staged_missing_dist(project_root: Path) -> bool:\n+    \"\"\"electron staged (package.json + install.js) but dist missing — blocked postinstall.\"\"\"\n+    electron_dir = _electron_dir(project_root)\n+    return (\n+        (electron_dir / \"package.json\").is_file()\n+        and (electron_dir / \"install.js\").is_file()\n+        and not _electron_dist_ok(project_root)\n+    )\n+\n+\n def _redownload_electron_dist(\n     project_root: Path,\n     env: dict,\n     *,\n     mirror: Optional[str] = None,\n ) -> bool:\n-    \"\"\"(Re)populate ``node_modules/electron/dist`` via electron's own downloader.\n-\n-    Since #38673 the desktop build pins ``build.electronDist`` to\n-    ``node_modules/electron/dist``, so electron-builder reads the Electron binary\n-    straight from there and never downloads it during ``npm run pack``. That dist\n-    tree is produced by the ``electron`` package's postinstall (``install.js``)\n-    during ``npm ci``. When that download is blocked or throttled (GitHub's\n-    release host is unreachable in some regions — #47266), the dist is missing\n-    and re-running ``pack`` only re-throws \"The specified electronDist does not\n-    exist\". The mirror fallback therefore has to drive *this* downloader, not\n-    another ``pack``.\n-\n-    No-op (returns True) when the dist binary is already present, so an unrelated\n-    build failure doesn't trigger a needless ~200 MB re-download. Otherwise drops\n-    any partial dist + version marker (electron's install.js short-circuits when\n-    ``path.txt`` already matches) and runs the downloader once, optionally via a\n-    mirror. Best-effort: never raises. Returns True iff the dist binary exists\n-    afterward.\n-    \"\"\"\n+    \"\"\"Best-effort: run electron's install.js to populate dist/ (optional mirror).\"\"\"\n     if _electron_dist_ok(project_root):\n         return True\n ","@@ -5211,6 +5208,15 @@ def _redownload_electron_dist(\n     return _electron_dist_ok(project_root)\n \n \n+def _try_redownload_electron_dist(project_root: Path, env: dict) -> bool:\n+    \"\"\"Canonical download, then fallback mirror unless the user pinned one.\"\"\"\n+    if _redownload_electron_dist(project_root, env):\n+        return True\n+    if env.get(\"ELECTRON_MIRROR\"):\n+        return False\n+    return _redownload_electron_dist(project_root, env, mirror=_ELECTRON_FALLBACK_MIRROR)\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.","@@ -5433,9 +5439,18 @@ def cmd_gui(args: argparse.Namespace):\n             nixos_env = _nixos_build_env()\n             install_result = _run_npm_install_deterministic(npm, PROJECT_ROOT, capture_output=False, env=nixos_env)\n             if install_result.returncode != 0:\n-                print(\"✗ Desktop dependency install failed\")\n-                print(f\"  Run manually:  cd {PROJECT_ROOT} && npm ci\")\n-                sys.exit(install_result.returncode or 1)\n+                if not _electron_pkg_staged_missing_dist(PROJECT_ROOT):\n+                    print(\"✗ Desktop dependency install failed\")\n+                    print(f\"  Run manually:  cd {PROJECT_ROOT} && npm ci\")\n+                    sys.exit(install_result.returncode or 1)\n+                repaired = _try_redownload_electron_dist(PROJECT_ROOT, env)\n+                if repaired:\n+                    print(\"  ⚠ Dependency install failed with a missing Electron dist; \"\n+                          \"repopulated it and continuing.\")\n+                else:\n+                    print(\"  ⚠ Dependency install failed with a missing Electron dist; \"\n+                          \"continuing to the build so electron-builder can attempt \"\n+                          \"the Electron fetch itself.\")\n \n             build_label = \"source build\" if source_mode else \"packaged app\"\n             print(f\"→ Building desktop {build_label}...\")","@@ -5451,31 +5466,15 @@ def cmd_gui(args: argparse.Namespace):\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\n-                # on the final `electron` -> `Hermes` rename: unpack-electron\n-                # extracted a partial tree (missing the 193 MB binary) from the\n-                # bad zip. We do NOT try to prove the zip is corrupt ourselves —\n-                # stdlib zipfile silently tolerates the prepended/concatenated\n-                # junk that is the most common corruption (a partial download\n-                # resumed into the same file), so a `testzip()` gate would pass\n-                # and never self-heal. Instead, on any packaged-build failure we\n-                # purge the version's cached zip + the half-written unpacked dir\n-                # and retry once: @electron/get re-downloads with its own SHASUM\n-                # verification, which is the real source of truth. If the\n-                # failure was something else, the clean re-download is harmless\n-                # and the retry fails the same way.\n-                purged = _purge_electron_build_cache(desktop_dir)\n-                # electronDist is pinned to node_modules/electron/dist (#38673):\n-                # electron-builder reads the Electron binary from there and `pack`\n-                # never downloads it, so purging the cache + re-running pack can't\n-                # by itself repopulate a missing/partial dist. When the dist is\n-                # actually gone, re-run electron's own downloader so the retry has\n-                # a binary to read. Gated on the dist check so an unrelated build\n-                # failure (tsc/vite) doesn't trigger a pointless ~200 MB refetch.\n+                # Corrupt cached Electron zip → partial unpack → ENOENT on rename.\n+                # stdlib zipfile won't catch the common concat-junk case, so purge\n+                # and retry once; @electron/get SHASUM is the real gate.\n+                purged: list[Path] = []\n                 restored = False\n                 if not _electron_dist_ok(PROJECT_ROOT):\n+                    purged = _purge_electron_build_cache(desktop_dir)\n                     restored = _redownload_electron_dist(PROJECT_ROOT, env)\n-                if purged or restored:\n+                if restored:\n                     print(\"  ⚠ Desktop build failed; refreshed the Electron download and retrying once...\")\n                     for p in purged:\n                         print(f\"    - {p}\")","@@ -5484,35 +5483,16 @@ def cmd_gui(args: argparse.Namespace):\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 and not source_mode and not env.get(\"ELECTRON_MIRROR\"):\n-                # Still failing and the user hasn't pinned a mirror: GitHub's\n-                # Electron release host is likely blocked/throttled (the repeating\n-                # \"retrying\" download log). Retry once via npmmirror.com — the\n-                # de-facto Electron community mirror (Alibaba). @electron/get\n-                # SHASUM-checks the download, but the SHASUMS come from the same\n-                # mirror, so that guards against a corrupt/partial download, NOT\n-                # a compromised mirror: reaching for it is an explicit trust\n-                # trade-off we only make AFTER the canonical GitHub download has\n-                # failed, and we never override a user-pinned ELECTRON_MIRROR.\n                 print(\"  ⚠ Desktop build still failing; the Electron download from \"\n                       \"GitHub looks blocked. Re-downloading via a public mirror \"\n                       \"(npmmirror.com)... (set ELECTRON_MIRROR to use another mirror)\")\n-                mirror = \"https://npmmirror.com/mirrors/electron/\"\n+                mirror = _ELECTRON_FALLBACK_MIRROR\n                 mirror_env = dict(env)\n                 mirror_env[\"ELECTRON_MIRROR\"] = mirror\n-                # electronDist is pinned (#38673), so `npm run pack` never\n-                # downloads Electron — the mirror only helps if it drives\n-                # electron's own downloader. Re-fetch the binary through the\n-                # mirror first; otherwise the retry just re-reads the same missing\n-                # dist and re-throws \"electronDist does not exist\" (#47266).\n-                have_dist = _electron_dist_ok(PROJECT_ROOT)\n-                if not have_dist:\n-                    have_dist = _redownload_electron_dist(PROJECT_ROOT, env, mirror=mirror)\n-                if have_dist:\n-                    _stop_desktop_processes_locking_build(desktop_dir)\n-                    build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=mirror_env, check=False)\n-                else:\n-                    print(\"  ✗ Could not re-download Electron from the mirror \"\n-                          \"(node_modules/electron/dist still missing)\")\n+                if not _electron_dist_ok(PROJECT_ROOT):\n+                    _redownload_electron_dist(PROJECT_ROOT, env, mirror=mirror)\n+                _stop_desktop_processes_locking_build(desktop_dir)\n+                build_result = subprocess.run([npm, \"run\", build_script], cwd=desktop_dir, env=mirror_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}\")"]},{"file":"scripts/install.ps1","hunks":["@@ -2161,12 +2161,10 @@ function Clear-ElectronBuildCache {\n     return $removed\n }\n \n-# Return the Electron package directory the desktop workspace installs. npm may\n-# nest workspace-only dev dependencies under apps\\desktop\\node_modules instead\n-# of hoisting them to the repo root; which layout you get depends on the npm\n-# version and what else is installed. apps\\desktop\\package.json points\n-# electron-builder's electronDist there, so prefer the workspace-local package\n-# and fall back to the root hoist.\n+# Last-resort Electron mirror after GitHub download fails (#47266).\n+$script:DesktopElectronFallbackMirror = \"https://npmmirror.com/mirrors/electron/\"\n+\n+# Electron package dir — workspace-local nest first, then root hoist.\n function Get-ElectronDir {\n     param([string]$InstallDir)\n     $desktopLocal = Join-Path $InstallDir 'apps\\desktop\\node_modules\\electron'","@@ -2174,11 +2172,7 @@ function Get-ElectronDir {\n     return (Join-Path $InstallDir 'node_modules\\electron')\n }\n \n-# True when the desktop workspace electronDist holds a usable Electron binary.\n-# electron-builder reads the binary from build.electronDist since #38673, so\n-# this is the exact file whose absence makes a pack fail with \"The specified\n-# electronDist does not exist\". A dist dir that exists but is missing\n-# electron.exe (partial extraction / aborted postinstall) is NOT ok.\n+# True when dist/ holds a usable Electron binary (#38673 / run-electron-builder.cjs).\n function Test-ElectronDist {\n     param([string]$InstallDir)\n     $electronDir = Get-ElectronDir -InstallDir $InstallDir","@@ -2186,22 +2180,7 @@ function Test-ElectronDist {\n     return (Test-Path -LiteralPath $distExe)\n }\n \n-# (Re)populate the desktop Electron dist via electron's own downloader.\n-#\n-# Since #38673 the desktop build pins build.electronDist, so electron-builder\n-# reads the Electron binary straight from there and never downloads it during\n-# `npm run pack`. That dist tree is produced by the electron package's\n-# postinstall (install.js) during `npm ci`. When that download is\n-# blocked/throttled (GitHub's release host is unreachable in some regions -\n-# #47266), dist is missing and re-running pack only re-throws \"The specified\n-# electronDist does not exist\". The mirror fallback therefore has to drive THIS\n-# downloader, not another pack.\n-#\n-# No-op (returns $true) when the dist binary is already present. Otherwise drops a\n-# partial dist + version marker (electron's install.js short-circuits when\n-# path.txt already matches) and runs the downloader once, optionally via a\n-# mirror. Best-effort: never throws. Returns $true iff the dist binary exists\n-# afterward.\n+# Best-effort: run electron/install.js to populate dist/ (optional mirror).\n function Restore-ElectronDist {\n     param([string]$InstallDir, [string]$Mirror)\n     if (Test-ElectronDist -InstallDir $InstallDir) { return $true }","@@ -2234,6 +2213,23 @@ function Restore-ElectronDist {\n     return (Test-Path -LiteralPath $distExe)\n }\n \n+function Test-ElectronPkgStagedMissingDist {\n+    param([string]$InstallDir)\n+    $electronDir = Get-ElectronDir -InstallDir $InstallDir\n+    return (\n+        (Test-Path -LiteralPath (Join-Path $electronDir 'package.json')) -and\n+        (Test-Path -LiteralPath (Join-Path $electronDir 'install.js')) -and\n+        (-not (Test-ElectronDist -InstallDir $InstallDir))\n+    )\n+}\n+\n+function Try-RestoreElectronDist {\n+    param([string]$InstallDir)\n+    if (Restore-ElectronDist -InstallDir $InstallDir) { return $true }\n+    if ($env:ELECTRON_MIRROR) { return $false }\n+    return Restore-ElectronDist -InstallDir $InstallDir -Mirror $script:DesktopElectronFallbackMirror\n+}\n+\n function Install-Desktop {\n     # Build apps/desktop into a launchable Hermes.exe. Only called from\n     # Stage-Desktop, which is itself only included in the manifest when","@@ -2329,10 +2325,16 @@ function Install-Desktop {\n         }\n         $ErrorActionPreference = $prevEAP\n         if ($code -ne 0) {\n-            Show-NpmCertHint ($npmOut -join \"`n\") | Out-Null\n-            throw \"desktop workspace npm install failed (exit $code) -- see lines above for cause\"\n+            if (Test-ElectronPkgStagedMissingDist -InstallDir $InstallDir) {\n+                Write-Warn \"Desktop dependency install failed with a missing Electron dist; attempting self-heal...\"\n+                Try-RestoreElectronDist -InstallDir $InstallDir | Out-Null\n+            } else {\n+                Show-NpmCertHint ($npmOut -join \"`n\") | Out-Null\n+                throw \"desktop workspace npm install failed (exit $code) -- see lines above for cause\"\n+            }\n+        } else {\n+            Write-Success \"Desktop workspace dependencies installed\"\n         }\n-        Write-Success \"Desktop workspace dependencies installed\"\n     } catch {\n         if ($prevEAP) { $ErrorActionPreference = $prevEAP }\n         Pop-Location","@@ -2375,57 +2377,34 @@ function Install-Desktop {\n         & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n         $code = $LASTEXITCODE\n         if ($code -ne 0) {\n-            # A corrupt cached Electron zip makes `pack` fail with an opaque\n-            # ENOENT on the final `electron` -> `Hermes` rename: app-builder's\n-            # unpack-electron extracted a partial tree (missing the binary) from\n-            # the bad zip, and re-running reuses the poisoned cache forever.\n-            # Purge the cached download + any stale unpacked output and retry\n-            # once; @electron/get re-downloads with its own SHASUM check. Without\n-            # this a corrupt download hard-fails the whole installer.\n-            $purged = @(Clear-ElectronBuildCache -DesktopDir $desktopDir)\n-            # electronDist is pinned to node_modules\\electron\\dist (#38673):\n-            # electron-builder reads the Electron binary from there and `pack`\n-            # never downloads it, so purging the cache + re-running pack can't by\n-            # itself repopulate a missing/partial dist. When the dist is actually\n-            # gone, re-run electron's own downloader so the retry has a binary to\n-            # read. Gated on the dist check so an unrelated build failure\n-            # (tsc/vite) doesn't trigger a pointless ~200MB refetch.\n+            $purged = @()\n             $restored = $false\n             if (-not (Test-ElectronDist -InstallDir $InstallDir)) {\n+                $purged = @(Clear-ElectronBuildCache -DesktopDir $desktopDir)\n                 $restored = Restore-ElectronDist -InstallDir $InstallDir\n             }\n-            if ($purged.Count -gt 0 -or $restored) {\n+            if ($restored) {\n                 Write-Warn \"Desktop build failed - refreshed the Electron download, retrying once:\"\n                 foreach ($p in $purged) { Write-Info \"  - $p\" }\n                 & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n                 $code = $LASTEXITCODE\n             }\n         }\n-        # Still failing and the user hasn't pinned their own mirror: GitHub's\n-        # Electron release host is likely blocked/throttled (the repeating\n-        # \"retrying\" log). Retry once via npmmirror.com — the de-facto Electron\n-        # community mirror (Alibaba). @electron/get SHASUM-checks the download,\n-        # but the SHASUMS come from the same mirror, so that guards against a\n-        # corrupt/partial download, NOT a compromised mirror: an explicit trust\n-        # trade-off we only make AFTER the canonical GitHub download has failed,\n-        # and we never override a user-pinned ELECTRON_MIRROR.\n         if ($code -ne 0 -and -not $env:ELECTRON_MIRROR) {\n-            $mirror = \"https://npmmirror.com/mirrors/electron/\"\n+            $mirror = $script:DesktopElectronFallbackMirror\n             Write-Warn \"Desktop build still failing - the Electron download from GitHub looks blocked.\"\n             Write-Warn \"Re-downloading Electron via a public mirror ($mirror), then rebuilding:\"\n             Write-Info \"  (set ELECTRON_MIRROR yourself to use a different/trusted mirror)\"\n-            # electronDist is pinned (#38673), so `npm run pack` never downloads\n-            # Electron - the mirror only helps if it drives electron's own\n-            # downloader. Re-fetch the binary through the mirror first; otherwise\n-            # the retry just re-reads the same missing dist and re-throws\n-            # \"The specified electronDist does not exist\" (#47266).\n-            $haveDist = Test-ElectronDist -InstallDir $InstallDir\n-            if (-not $haveDist) { $haveDist = Restore-ElectronDist -InstallDir $InstallDir -Mirror $mirror }\n-            if ($haveDist) {\n+            if (-not (Test-ElectronDist -InstallDir $InstallDir)) {\n+                Restore-ElectronDist -InstallDir $InstallDir -Mirror $mirror | Out-Null\n+            }\n+            $prevMirror = $env:ELECTRON_MIRROR\n+            $env:ELECTRON_MIRROR = $mirror\n+            try {\n                 & $npmExe run pack 2>&1 | ForEach-Object { \"$_\" } | Tee-Object -FilePath $buildLog\n                 $code = $LASTEXITCODE\n-            } else {\n-                Write-Warn \"Could not re-download Electron from the mirror (node_modules\\electron\\dist still missing)\"\n+            } finally {\n+                $env:ELECTRON_MIRROR = $prevMirror\n             }\n         }\n         $ErrorActionPreference = $prevEAP"]},{"file":"scripts/install.sh","hunks":["@@ -2398,21 +2398,10 @@ _desktop_pack() {\n     fi\n }\n \n-# Public Electron mirror used as a last-resort fallback when GitHub's release\n-# host is blocked/throttled (the repeating \"retrying\" symptom). npmmirror.com is\n-# the de-facto Electron community mirror (Alibaba). @electron/get SHASUM-checks\n-# the download, but the SHASUMS come from the same mirror — that guards against a\n-# corrupt/partial download, NOT a compromised mirror. Reaching for it is an\n-# explicit trust trade-off we only make AFTER the canonical GitHub download has\n-# failed, and we never override a user-pinned ELECTRON_MIRROR.\n+# Last-resort Electron mirror after GitHub download fails (#47266).\n DESKTOP_ELECTRON_FALLBACK_MIRROR=\"https://npmmirror.com/mirrors/electron/\"\n \n-# Return the Electron package directory the desktop workspace installs. npm may\n-# nest workspace-only dev dependencies under apps/desktop/node_modules instead\n-# of hoisting them to the repo root; which layout you get depends on the npm\n-# version and what else is installed. apps/desktop/package.json points\n-# electron-builder's electronDist there, so prefer the workspace-local package\n-# and fall back to the root hoist. $1 = the workspace root holding node_modules.\n+# Electron package dir — workspace-local nest first, then root hoist.\n _electron_dir() {\n     local install_dir=\"$1\"\n     if [ -d \"$install_dir/apps/desktop/node_modules/electron\" ]; then","@@ -2422,12 +2411,7 @@ _electron_dir() {\n     fi\n }\n \n-# True (returns 0) when the desktop workspace electronDist holds a usable\n-# Electron binary. electron-builder reads the binary from build.electronDist\n-# since #38673, so this is the exact file whose absence makes a pack fail with\n-# \"The specified electronDist does not exist\". A dist dir that exists but is\n-# missing the binary (partial extraction / aborted postinstall) is NOT ok.\n-# $1 = the workspace root holding node_modules.\n+# True when dist/ holds a usable Electron binary (#38673 / run-electron-builder.cjs).\n _electron_dist_ok() {\n     local install_dir=\"$1\"\n     local electron_dir","@@ -2439,22 +2423,7 @@ _electron_dist_ok() {\n     fi\n }\n \n-# (Re)populate the desktop Electron dist via electron's own downloader.\n-#\n-# Since #38673 the desktop build pins build.electronDist, so electron-builder\n-# reads the Electron binary straight from there and never downloads it during\n-# `npm run pack`. That dist tree is produced by the electron package's\n-# postinstall (install.js) during `npm ci`. When that download is\n-# blocked/throttled (GitHub's release host is unreachable in some regions -\n-# #47266), dist is missing and re-running pack only re-throws \"The specified\n-# electronDist does not exist\". The mirror fallback therefore has to drive THIS\n-# downloader, not another pack.\n-#\n-# No-op (returns 0) when the dist binary is already present. Otherwise drops a\n-# partial dist + version marker (electron's install.js short-circuits when\n-# path.txt already matches) and runs the downloader once. $1 = the workspace root\n-# holding node_modules; optional $2 = an ELECTRON_MIRROR base URL. Best-effort:\n-# returns 0 iff the dist binary exists afterward.\n+# Best-effort: run electron/install.js to populate dist/ (optional mirror).\n _restore_electron_dist() {\n     local install_dir=\"$1\"\n     local mirror=\"${2:-}\"","@@ -2476,6 +2445,19 @@ _restore_electron_dist() {\n     _electron_dist_ok \"$install_dir\"\n }\n \n+_electron_pkg_staged_missing_dist() {\n+    local install_dir=\"$1\"\n+    local electron_dir\n+    electron_dir=\"$(_electron_dir \"$install_dir\")\"\n+    [ -f \"$electron_dir/package.json\" ] && [ -f \"$electron_dir/install.js\" ] && ! _electron_dist_ok \"$install_dir\"\n+}\n+\n+_restore_electron_dist_with_fallback() {\n+    local install_dir=\"$1\"\n+    _restore_electron_dist \"$install_dir\" \\\n+        || { [ -z \"${ELECTRON_MIRROR:-}\" ] && _restore_electron_dist \"$install_dir\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; }\n+}\n+\n # Build apps/desktop into a launchable native app. Mirrors install.ps1's\n # Install-Desktop: a root-level npm install so the apps/* workspace resolves\n # the desktop's own deps (Electron ~150MB), then `npm run pack`","@@ -2517,7 +2499,12 @@ install_desktop() {\n     #    `tsc -b` failing with no obvious cause. Fall back to `npm install`\n     #    only if `npm ci` is unavailable or the lockfile is out of sync.\n     log_info \"Installing desktop workspace dependencies (includes Electron ~150MB, 1-3min)...\"\n-    ( cd \"$INSTALL_DIR\" && npm ci ) || ( cd \"$INSTALL_DIR\" && npm install ) || {\n+    if ( cd \"$INSTALL_DIR\" && npm ci ) || ( cd \"$INSTALL_DIR\" && npm install ); then\n+        log_success \"Desktop workspace dependencies installed\"\n+    elif _electron_pkg_staged_missing_dist \"$INSTALL_DIR\"; then\n+        log_warn \"Desktop dependency install failed with a missing Electron dist; attempting self-heal...\"\n+        _restore_electron_dist_with_fallback \"$INSTALL_DIR\" || true\n+    else\n         log_error \"Desktop workspace npm install failed\"\n         # Common cause: a previous 'sudo npm'/'sudo npx' left root-owned files in\n         # ~/.npm, so this non-root install can't write the shared cache. npm hides","@@ -2530,8 +2517,7 @@ install_desktop() {\n         log_info \"Then re-run this installer, or build manually:\"\n         log_info \"  cd \\\"$INSTALL_DIR\\\" && npm ci && cd apps/desktop && npm run pack\"\n         return 1\n-    }\n-    log_success \"Desktop workspace dependencies installed\"\n+    fi\n \n     # 2. Build, with up to three escalating attempts so a transient/blocked\n     #    Electron download self-heals instead of failing the whole install:","@@ -2545,21 +2531,13 @@ install_desktop() {\n     if _desktop_pack \"$desktop_dir\"; then\n         pack_ok=true\n     else\n-        # (b) Corrupt cached Electron zip is the most common self-healable cause.\n-        local purged\n-        purged=\"$(clear_electron_build_cache \"$desktop_dir\")\"\n-        # electronDist is pinned to node_modules/electron/dist (#38673):\n-        # electron-builder reads the binary from there and `pack` never downloads\n-        # it, so purging the cache + re-running pack can't by itself repopulate a\n-        # missing/partial dist. When the dist is actually gone, re-run electron's\n-        # own downloader so the retry has a binary to read. Gated on the dist\n-        # check so an unrelated build failure (tsc/vite) doesn't trigger a\n-        # pointless ~200MB refetch.\n+        local purged=\"\"\n         local restored=false\n         if ! _electron_dist_ok \"$INSTALL_DIR\"; then\n+            purged=\"$(clear_electron_build_cache \"$desktop_dir\")\"\n             if _restore_electron_dist \"$INSTALL_DIR\"; then restored=true; fi\n         fi\n-        if [ -n \"$purged\" ] || [ \"$restored\" = true ]; then\n+        if [ \"$restored\" = true ]; then\n             log_warn \"Desktop build failed; refreshed the Electron download and retrying once...\"\n             if _desktop_pack \"$desktop_dir\"; then\n                 pack_ok=true","@@ -2567,27 +2545,14 @@ install_desktop() {\n         fi\n     fi\n \n-    # (c) Still failing and the user hasn't pinned their own mirror: the GitHub\n-    #     release host is likely blocked/throttled. Re-download the Electron\n-    #     binary via a public mirror, then retry. The mirror MUST drive\n-    #     electron's own downloader — `npm run pack` reads the pinned electronDist\n-    #     and never downloads, so a mirror passed only to pack is a no-op (#47266).\n+    # (c) GitHub blocked → mirror fallback (#47266).\n     if [ \"$pack_ok\" = false ] && [ -z \"${ELECTRON_MIRROR:-}\" ]; then\n         log_warn \"Desktop build still failing — the Electron download from GitHub looks blocked.\"\n         log_warn \"Re-downloading Electron via a public mirror ($DESKTOP_ELECTRON_FALLBACK_MIRROR), then rebuilding...\"\n         log_warn \"  (set ELECTRON_MIRROR yourself to use a different/trusted mirror)\"\n-        local have_dist=false\n-        if _electron_dist_ok \"$INSTALL_DIR\"; then\n-            have_dist=true\n-        elif _restore_electron_dist \"$INSTALL_DIR\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n-            have_dist=true\n-        fi\n-        if [ \"$have_dist\" = true ]; then\n-            if _desktop_pack \"$desktop_dir\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n-                pack_ok=true\n-            fi\n-        else\n-            log_warn \"Could not re-download Electron from the mirror (node_modules/electron/dist still missing)\"\n+        _electron_dist_ok \"$INSTALL_DIR\" || _restore_electron_dist \"$INSTALL_DIR\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\" || true\n+        if _desktop_pack \"$desktop_dir\" \"$DESKTOP_ELECTRON_FALLBACK_MIRROR\"; then\n+            pack_ok=true\n         fi\n     fi\n "]},{"file":"tests/hermes_cli/test_gui_command.py","hunks":["@@ -485,13 +485,15 @@ def test_gui_retries_pack_once_after_purging_build_cache(tmp_path, monkeypatch):\n          patch(\"hermes_cli.main._desktop_linux_sandbox_fixup\", return_value=True), \\\n          patch(\"hermes_cli.main._write_desktop_build_stamp\"), \\\n          patch(\"hermes_cli.main._purge_electron_build_cache\", return_value=[Path(\"/c/electron.zip\")]) as mock_purge, \\\n+         patch(\"hermes_cli.main._electron_dist_ok\", return_value=False), \\\n+         patch(\"hermes_cli.main._redownload_electron_dist\", return_value=True), \\\n          patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_fail, pack_ok, launch_ok]) as mock_run, \\\n          pytest.raises(SystemExit) as exc:\n         cli_main.cmd_gui(_ns())\n \n     assert exc.value.code == 0\n     mock_purge.assert_called_once()\n-    # pack(fail) → purge → pack(ok) → launch = 3 subprocess.run calls\n+    # pack(fail) → repair succeeds → pack(ok) → launch = 3 subprocess.run calls\n     assert mock_run.call_count == 3\n     assert mock_run.call_args_list[0].args[0] == [\"/usr/bin/npm\", \"run\", \"pack\"]\n     assert mock_run.call_args_list[1].args[0] == [\"/usr/bin/npm\", \"run\", \"pack\"]","@@ -535,10 +537,12 @@ def test_gui_redownloads_electron_via_mirror_then_repacks(tmp_path, monkeypatch,\n     assert \"Desktop GUI build failed\" in capsys.readouterr().out\n \n \n-def test_gui_skips_pack_when_electron_redownload_unrecoverable(tmp_path, monkeypatch, capsys):\n-    \"\"\"When the Electron binary can't be fetched at all (mirror also blocked),\n-    skip the pointless final pack — it would just re-throw the same missing\n-    electronDist — and fail with a clear message instead.\"\"\"\n+def test_gui_retries_pack_under_mirror_even_when_prefetch_blocked(tmp_path, monkeypatch, capsys):\n+    \"\"\"When electron's own downloader can't fetch the binary (even via the\n+    mirror), still retry pack under ELECTRON_MIRROR: the build resolves\n+    electronDist dynamically and lets electron-builder fetch Electron itself\n+    via @electron/get, which honors the mirror. That retry is no longer\n+    pointless (it was, back when electronDist was a static path).\"\"\"\n     root = _make_desktop_tree(tmp_path)\n     monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n     _make_packaged_executable(root, monkeypatch, platform=\"linux\")","@@ -553,17 +557,96 @@ def test_gui_skips_pack_when_electron_redownload_unrecoverable(tmp_path, monkeyp\n          patch(\"hermes_cli.main._purge_electron_build_cache\", return_value=[]), \\\n          patch(\"hermes_cli.main._electron_dist_ok\", return_value=False), \\\n          patch(\"hermes_cli.main._redownload_electron_dist\", return_value=False), \\\n-         patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_fail]) as mock_run, \\\n+         patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_fail, pack_fail]) as mock_run, \\\n          pytest.raises(SystemExit) as exc:\n         cli_main.cmd_gui(_ns())\n \n     assert exc.value.code == 1\n-    # Only the initial pack ran; both retries were skipped because no binary\n-    # could be produced.\n-    assert mock_run.call_count == 1\n-    out = capsys.readouterr().out\n-    assert \"Could not re-download Electron from the mirror\" in out\n-    assert \"Desktop GUI build failed\" in out\n+    # Initial pack + mirror-driven pack = 2; the mirror retry runs even though\n+    # the pre-fetch failed, so electron-builder gets a shot at downloading.\n+    assert mock_run.call_count == 2\n+    assert \"ELECTRON_MIRROR\" not in (mock_run.call_args_list[0].kwargs.get(\"env\") or {})\n+    assert mock_run.call_args_list[1].kwargs[\"env\"][\"ELECTRON_MIRROR\"]\n+    assert \"Desktop GUI build failed\" in capsys.readouterr().out\n+\n+\n+def test_gui_install_failure_self_heals_electron_and_continues(tmp_path, monkeypatch, capsys):\n+    \"\"\"npm ci failing on electron's blocked binary download must NOT abort the\n+    install: with the electron package staged, repopulate its dist and continue\n+    to the build instead of sys.exit-ing before pack ever runs (#47266/#48021).\"\"\"\n+    root = _make_desktop_tree(tmp_path)\n+    monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n+    packaged_exe = _make_packaged_executable(root, monkeypatch, platform=\"linux\")\n+    # electron package staged on disk (postinstall download was the casualty).\n+    (root / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\").mkdir(parents=True)\n+    (root / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\" / \"package.json\").write_text(\"{}\", encoding=\"utf-8\")\n+    (root / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\" / \"install.js\").write_text(\"\", encoding=\"utf-8\")\n+\n+    install_fail = subprocess.CompletedProcess([\"npm\", \"ci\"], 1)\n+    pack_ok = subprocess.CompletedProcess([\"npm\", \"run\", \"pack\"], 0)\n+    launch_ok = subprocess.CompletedProcess([str(packaged_exe)], 0)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/npm\"), \\\n+         patch(\"hermes_cli.main._run_npm_install_deterministic\", return_value=install_fail), \\\n+         patch(\"hermes_cli.main._desktop_linux_sandbox_fixup\", return_value=True), \\\n+         patch(\"hermes_cli.main._write_desktop_build_stamp\"), \\\n+         patch(\"hermes_cli.main._electron_dist_ok\", return_value=False), \\\n+         patch(\"hermes_cli.main._try_redownload_electron_dist\", return_value=True) as mock_dl, \\\n+         patch(\"hermes_cli.main.subprocess.run\", side_effect=[pack_ok, launch_ok]) as mock_run, \\\n+         pytest.raises(SystemExit) as exc:\n+        cli_main.cmd_gui(_ns())\n+\n+    assert exc.value.code == 0\n+    mock_dl.assert_called()  # tried to repopulate the dist\n+    # pack + launch ran — the install failure did NOT abort the build.\n+    assert mock_run.call_count == 2\n+    assert \"repopulated\" in capsys.readouterr().out.lower()\n+\n+\n+def test_gui_install_failure_hard_fails_when_electron_not_staged(tmp_path, monkeypatch, capsys):\n+    \"\"\"A dependency-install failure where electron never even staged is a genuine\n+    error (not a blocked binary download) — hard-fail with guidance, don't try to\n+    self-heal a tree that isn't there.\"\"\"\n+    root = _make_desktop_tree(tmp_path)\n+    monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n+    _make_packaged_executable(root, monkeypatch, platform=\"linux\")\n+\n+    install_fail = subprocess.CompletedProcess([\"npm\", \"ci\"], 1)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/npm\"), \\\n+         patch(\"hermes_cli.main._run_npm_install_deterministic\", return_value=install_fail), \\\n+         patch(\"hermes_cli.main.subprocess.run\") as mock_run, \\\n+         pytest.raises(SystemExit) as exc:\n+        cli_main.cmd_gui(_ns())\n+\n+    assert exc.value.code == 1\n+    mock_run.assert_not_called()  # build never started\n+    assert \"Desktop dependency install failed\" in capsys.readouterr().out\n+\n+\n+def test_gui_install_failure_hard_fails_when_electron_dist_exists(tmp_path, monkeypatch, capsys):\n+    \"\"\"If npm install fails but Electron dist is already present, don't classify\n+    it as the blocked-download shape; fail fast as a generic install error.\"\"\"\n+    root = _make_desktop_tree(tmp_path)\n+    monkeypatch.setattr(cli_main, \"PROJECT_ROOT\", root)\n+    _make_packaged_executable(root, monkeypatch, platform=\"linux\")\n+    electron_dir = root / \"apps\" / \"desktop\" / \"node_modules\" / \"electron\"\n+    electron_dir.mkdir(parents=True)\n+    (electron_dir / \"package.json\").write_text(\"{}\", encoding=\"utf-8\")\n+    (electron_dir / \"install.js\").write_text(\"\", encoding=\"utf-8\")\n+\n+    install_fail = subprocess.CompletedProcess([\"npm\", \"ci\"], 1)\n+\n+    with patch(\"hermes_cli.main.shutil.which\", return_value=\"/usr/bin/npm\"), \\\n+         patch(\"hermes_cli.main._run_npm_install_deterministic\", return_value=install_fail), \\\n+         patch(\"hermes_cli.main._electron_dist_ok\", return_value=True), \\\n+         patch(\"hermes_cli.main.subprocess.run\") as mock_run, \\\n+         pytest.raises(SystemExit) as exc:\n+        cli_main.cmd_gui(_ns())\n+\n+    assert exc.value.code == 1\n+    mock_run.assert_not_called()\n+    assert \"Desktop dependency install failed\" in capsys.readouterr().out\n \n \n def test_gui_does_not_override_user_electron_mirror(tmp_path, monkeypatch, capsys):"]},{"file":"tests/test_desktop_electron_pin.py","hunks":["@@ -96,40 +96,40 @@ def test_lockfile_resolves_the_pinned_electron():\n     )\n \n \n-def test_electron_dist_matches_lockfile_install_location():\n-    \"\"\"build.electronDist must point at where the lockfile installs Electron.\n-\n-    electron-builder copies the unpacked Electron from ``build.electronDist``\n-    (resolved relative to ``apps/desktop``). npm workspace hoisting is not\n-    deterministic across machines/npm versions: it may nest Electron under\n-    ``apps/desktop/node_modules/electron`` or hoist it to the repo root. If\n-    electronDist points at one location while the lockfile installs at the\n-    other, packaging fails with ``The specified electronDist does not exist`` —\n-    the \"Building desktop app\" failure reported after the June lockfile\n-    regeneration floated Electron and reshuffled the hoist. Lock the two\n-    together so a hoist change (root <-> nested) can't silently break the path\n-    again.\n-    \"\"\"\n-    if not ROOT_LOCK.is_file():\n-        pytest.skip(\"root package-lock.json not present\")\n-    electron_dist = _desktop_pkg().get(\"build\", {}).get(\"electronDist\")\n-    assert electron_dist, \"build.electronDist is missing\"\n+DESKTOP_DIR = REPO_ROOT / \"apps\" / \"desktop\"\n+ELECTRON_BUILDER_WRAPPER = DESKTOP_DIR / \"scripts\" / \"run-electron-builder.cjs\"\n \n-    lock = json.loads(ROOT_LOCK.read_text(encoding=\"utf-8\"))\n-    electron_paths = [\n-        path\n-        for path in lock.get(\"packages\", {})\n-        if path.endswith(\"node_modules/electron\")\n-    ]\n-    assert electron_paths, \"no electron entry found in package-lock.json\"\n-\n-    desktop_dir = REPO_ROOT / \"apps\" / \"desktop\"\n-    # electronDist is resolved relative to the apps/desktop project dir.\n-    configured = (desktop_dir / electron_dist).resolve()\n-    # Where the lockfile actually places Electron's unpacked dist.\n-    installed = {(REPO_ROOT / p / \"dist\").resolve() for p in electron_paths}\n-    assert configured in installed, (\n-        f\"build.electronDist={electron_dist!r} resolves to {configured}, but the \"\n-        f\"lockfile installs Electron at {sorted(str(p) for p in installed)}. \"\n-        \"electron-builder will fail with 'electronDist does not exist'.\"\n+\n+def test_no_static_electron_dist_that_can_drift():\n+    \"\"\"build.electronDist must not be a static path — hoisting is non-deterministic.\"\"\"\n+    assert \"electronDist\" not in _desktop_pkg().get(\"build\", {}), (\n+        \"build.electronDist is hardcoded again. npm hoisting is non-deterministic, \"\n+        \"so a static path silently breaks packaging when the layout changes. Let \"\n+        \"scripts/run-electron-builder.cjs resolve it dynamically instead.\"\n+    )\n+\n+\n+def test_builder_script_routes_through_dynamic_resolver():\n+    \"\"\"npm run builder must invoke run-electron-builder.cjs, not bare electron-builder.\"\"\"\n+    builder = _desktop_pkg().get(\"scripts\", {}).get(\"builder\", \"\")\n+    assert \"run-electron-builder.cjs\" in builder, (\n+        f\"the 'builder' script must run scripts/run-electron-builder.cjs, got \"\n+        f\"{builder!r}\"\n+    )\n+    assert ELECTRON_BUILDER_WRAPPER.is_file(), (\n+        f\"missing dynamic-resolver wrapper at {ELECTRON_BUILDER_WRAPPER}\"\n+    )\n+\n+\n+def test_resolver_uses_node_module_resolution():\n+    \"\"\"Wrapper must resolve electron via require.resolve and pass -c.electronDist.\"\"\"\n+    src = ELECTRON_BUILDER_WRAPPER.read_text(encoding=\"utf-8\")\n+    assert 'require.resolve(\"electron/package.json\")' in src, (\n+        \"run-electron-builder.cjs must resolve electron via \"\n+        \"require.resolve('electron/package.json') to stay hoist-proof.\"\n+    )\n+    # And it must hand the resolved dist to electron-builder as an override.\n+    assert \"-c.electronDist=\" in src, (\n+        \"run-electron-builder.cjs must pass the resolved dist to electron-builder \"\n+        \"via -c.electronDist.\"\n     )\n"]}]},{"kind":"pr","number":49152,"title":"fix(desktop): pre-fetch Electron dist after clean install on update (#47917)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#48091","number":48091},{"kind":"pr","value":"#47917","number":47917},{"kind":"pr","value":"#48091","number":48091}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}