Skip to content

ci: the web suite runs only on Node 24, so Node-26-only breakage is invisible to the gate #2074

Description

@svengerber

Split out of #2058 / #2073 rather than grown into that PR's scope.

The blind spot

Four web tests were red on a clean main for every contributor on Node 26 and
green in CI, for a week, because CI pins one Node version:

.github/workflows/ci.yml:159        node-version: 24
.github/workflows/e2e.yml:87        node-version: 24
.github/workflows/desktop.yml:45    node-version: 24

#2073 fixes that instance — Node 26 ships an inert localStorage global that
vitest's happy-dom environment declines to replace, so happy-dom tests read
undefined. It does not fix the reason nobody found out from CI.

Nothing pins the local version either: no .nvmrc, no engines in
web/package.json. So contributors drift onto whatever Node they have, and
AGENTS.md tells every one of them to trust make ci locally before pushing. A
suite that is red for reasons unrelated to your change is a suite people learn
to ignore — which is the part of #2058 worth more than the four tests.

Why this shape of bug will recur

The failure was not a broken API. Node 26 added globals (Storage,
localStorage, sessionStorage), and adding one was enough: vitest's
populateGlobal skips any window key the platform already defines unless the
key is on its hardcoded allow-list, and localStorage is not. Every future Node
that lands a web global can shadow happy-dom the same way, silently — and
silently is the operative word: profile.svelte.ts and palette.svelte.ts guard
on typeof localStorage === 'undefined', so the no-storage branch passes on
default data rather than erroring. Only a test asserting storage round-trips
goes red.

What to decide

Two candidate remedies, and they are not exclusive:

  1. Add the current Node to the web job's matrix (node-version: [24, 26]).
    Catches this class at the gate. Costs one more web-suite run per PR — the Go,
    e2e and vulncheck jobs need not be duplicated, and this repo already cancels
    superseded runs (Revert "ci: report e2e on the PRs where it does not run" #963). The open question is whether to gate the merge on the
    newer entry or let it be advisory, since a Node-26 regression in a
    dependency would then block unrelated PRs.
  2. Pin the local Node so the two gates cannot disagree — .nvmrc plus an
    engines field. Cheap, but advisory: no tooling here reads .nvmrc
    automatically, and pinning to 24 tells contributors on 26 that their working
    setup is unsupported when, after fix(web): restore happy-dom's localStorage over Node 26's inert global #2073, it is not.

My read is that (1) is the durable one and (2) alone would only have relabelled
this bug rather than surfaced it. Worth a contributor's call on whether the
second matrix entry blocks or informs, which is why this is an issue and not a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    infraCI, deploy, tooling

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions