Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ jobs:
if: needs.detect.outputs.installer == 'true'
uses: ./.github/workflows/installer-tests.yml

desktop-install-windows:
name: Desktop install (Windows)
needs: detect
# The only `npm ci` in CI that runs on Windows. Same `frontend` gate as
# js-tests, so Python-only PRs skip it. See the workflow header for what it
# does and does not cover — notably it cannot reproduce the missing-VC++
# runtime failure, because the runner image always has it.
if: needs.detect.outputs.frontend == 'true'
uses: ./.github/workflows/desktop-install-windows.yml
Comment thread
dizhaky marked this conversation as resolved.

e2e-desktop:
name: Desktop E2E
needs: detect
Expand Down
112 changes: 112 additions & 0 deletions .github/workflows/desktop-install-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Desktop install (Windows)

# A real `npm ci` on a Windows runner, which nothing else in CI does.
#
# Everything JS/TS runs on ubuntu-latest, so the entire Windows dependency
# path — Electron's postinstall unpacking a dist through `@electron/get` and
# its native `@electron-internal/extract-zip` addon — has never been exercised
# by CI. The Windows job that does exist (installer-tests.yml) only runs
# PowerShell tests against `scripts/install.ps1` and never installs anything.
#
# ── What this catches ───────────────────────────────────────────────────────
#
# * A dependency that resolves on Linux but not on Windows: a missing
# platform-specific optional package, a lockfile regenerated without the
# win32 entries, an engine constraint that only bites here.
# * Electron's postinstall failing or silently not producing a binary — the
# step below asserts the unpacked executable exists rather than trusting
# `npm ci`'s exit code, because a skipped postinstall exits 0.
# * The version-pin contract, checked on the platform it is about:
# `desktop-electron-pin.test.ts` asserts the dependency, the lockfile and
# `build.electronVersion` all agree, which is what keeps electron-builder
# from packaging a different Electron than `npm ci` installed.
#
# ── What this does NOT catch, deliberately stated ───────────────────────────
#
# It cannot reproduce the `ERR_DLOPEN_FAILED loading index.win32-x64-msvc.node`
# failure that motivated `apps/desktop/scripts/assert-win-vcruntime.mjs`. That
# addon links against `VCRUNTIME140.dll` from the Visual C++ 2015-2022
# Redistributable, and the `windows-latest` image ships Visual Studio Build
# Tools, so the runtime is always present here. This job would stay green on a
# regression that breaks every clean developer machine. Reproducing that needs
# a runner image without the redistributable; until then the README
# prerequisite and the prebuilder guard are what cover it.
#
# Gated on the `frontend` lane, so a Python-only PR does not pay for a Windows
# runner. Windows runners are slower than ubuntu and `npm ci` here is the long
# pole, hence the wider timeout.

on:
workflow_call:

permissions:
contents: read

concurrency:
group: desktop-install-windows-${{ github.ref }}
cancel-in-progress: true

jobs:
install:
name: npm ci + desktop platform tests
runs-on: windows-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 26
cache: npm

- name: grab npm 12
run: npm i -g npm@12

# Deliberately NOT --ignore-scripts: the postinstall is the thing under
# test. This is the only place in CI where Electron is fetched and
# unpacked on Windows.
#
# Retried inline rather than via .github/actions/retry: that composite is
# `shell: bash`, and no Windows job in this repo has ever used it, so
# this job would be its first — an unproven path inside a job that is
# itself new. The semantics (3 attempts, 10s apart) match the action's
# defaults, and an Electron dist download is exactly the flaky-network
# case retries exist for.
- name: npm ci (with postinstall)
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
for ($i = 1; $i -le 3; $i++) {
npm ci
if ($LASTEXITCODE -eq 0) { exit 0 }
if ($i -lt 3) {
Write-Host "::warning::npm ci failed (attempt $i of 3); retrying in 10s"
Start-Sleep -Seconds 10
}
}
Write-Error 'npm ci failed after 3 attempts'
exit 1

# `npm ci` exits 0 when a postinstall is skipped, so its exit code alone
# does not prove Electron was unpacked. Assert the artifact.
- name: Electron binary was actually unpacked
shell: pwsh
run: |
$exe = Join-Path $PWD 'node_modules/electron/dist/electron.exe'
if (-not (Test-Path $exe)) {
Write-Error "electron.exe missing at $exe — postinstall did not unpack a dist"
exit 1
}
$size = (Get-Item $exe).Length
Write-Host "electron.exe present ($([math]::Round($size / 1MB, 1)) MB)"

# Runs the addon-load guard for real on Windows. Green here only proves
# it does not false-positive on a prepared machine (see the header).
- name: Native-unpacker guard is satisfied
run: node scripts/assert-win-vcruntime.mjs
working-directory: apps/desktop

# The `electron` vitest project: desktop-electron-pin.test.ts plus the
# scripts/*.test.mjs build guards.
- name: Desktop platform tests
run: npm run --prefix apps/desktop check:test:desktop:platforms
44 changes: 44 additions & 0 deletions docs/system-log/2026-08-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,47 @@ I should have checked the diff before naming a culprit.
- **Follow-up:** a `windows-latest` desktop install job would catch resolution
regressions but not this bug, since the runner has the redistributable;
reproducing it in CI needs a runner image without it. Not added.

## 2026-08-09T16:20:00Z — Add the Windows desktop-install CI job (Claude Code)

- **Agent/tool:** Claude Code
- **Repos:** hermes-agent
- **Done:**
- **Added `.github/workflows/desktop-install-windows.yml`** — the only
`npm ci` in CI that runs on Windows. Every JS/TS lane runs on
`ubuntu-latest`, so the whole Windows dependency path (Electron's
postinstall unpacking a dist through `@electron/get` and its native
`@electron-internal/extract-zip` addon) had never been exercised. The one
existing Windows job, `installer-tests.yml`, runs PowerShell tests against
`install.ps1` and installs nothing.
- **Wired into `ci.yml`** behind the same `frontend` lane as `js-tests`, so a
Python-only PR does not pay for a Windows runner. 40-minute timeout —
Windows runners are slower and `npm ci` here is the long pole.
- **Asserts the artifact, not the exit code.** `npm ci` exits 0 when a
postinstall is skipped, so a step checks `node_modules/electron/dist/
electron.exe` exists and prints its size. Without that, a silently skipped
unpack would read as a green job.
- **Runs the addon-load guard and the `electron` vitest project** on Windows
— `desktop-electron-pin.test.ts` plus the `scripts/*.test.mjs` build
guards, which is the platform that pin contract is actually about.
- **Did not use `.github/actions/retry`.** That composite is `shell: bash`
and no Windows job in this repo has ever used it, so this job would have
been its first — an unproven path inside a job that is itself new and
unrunnable here. Replaced with an inline `pwsh` loop matching the action's
defaults (3 attempts, 10s apart); an Electron dist download is exactly the
flaky-network case retries exist for.
- **Stated in the workflow header what it does *not* catch:** it cannot
reproduce the `ERR_DLOPEN_FAILED` failure from the 15:10 entry, because
`windows-latest` ships Visual Studio Build Tools and therefore always has
`VCRUNTIME140.dll`. The job would stay green on a regression that breaks
every clean developer machine. Reproducing that needs a runner image
without the redistributable.
- **Verification is structural only, and that is the honest limit.** Both
workflow files parse as YAML; the job graph, step shells, pinned action
SHAs (matching 26 and 6 existing usages respectively), the `frontend` gate,
the `check:test:desktop:platforms` script and every referenced path were
checked to exist. **The job has never been executed** — there is no Windows
runner here — so its first real run is on the PR itself.
- **Follow-up:** if the first run is red, the likely causes in order are the
`npm ci` duration against the 40-minute timeout, and Windows-specific
behaviour in a postinstall this repo has never run there.
Loading