Skip to content

Fix CI: upgrade Yarn to 4.14.1 + install libusb for native modules - #1958

Merged
codingki merged 4 commits into
mainfrom
fix/yarn-node25-ebadf
Apr 30, 2026
Merged

codingki merged 4 commits into
mainfrom
fix/yarn-node25-ebadf

Conversation

@codingki

Copy link
Copy Markdown
Member

Summary

CI was failing on the test (25) job (Node 25 matrix entry) during yarn install with three build errors:

YN0009: usb@npm:2.9.0 couldn't be built successfully
YN0009: unrs-resolver@npm:1.11.1 couldn't be built successfully
YN0009: node-hid@npm:2.1.2 couldn't be built successfully
YN0000: · Failed with errors in 22s 177ms

This had been blocking every PR.

Root cause 1: EBADF in PnP zip-loaded CJS modules on Node ≥25.7 / ≥24.15

The unrs-resolver failure (and the cascading Error: EBADF: bad file descriptor, fstat next to usb / node-hid) is a Yarn PnP / Node.js incompatibility, not a build script bug. Node's ESM loader started reading CJS source through an unpatched fs.readFileSync, which can't fstat Yarn's zip-backed file descriptors:

Error: EBADF: bad file descriptor, fstat
    at tryStatSync (node:fs:386:25)
    at readFileSync (node:fs:442:17)
    at getSourceSync (node:internal/modules/esm/load:37:14)
    at createCJSModuleWrap (node:internal/modules/esm/translators:211:32)
    at ModuleLoader.commonjsStrategy (node:internal/modules/esm/translators:350:10)
    ...
Node.js v25.9.0

Tracked in yarnpkg/berry#7065, #7103 and nodejs/node#62012. Fixed in Yarn 4.14.1, which detects affected Node versions (HAS_BROKEN_FSTAT_FOR_ZIP_FDS = major > 25 || (major === 25 && minor >= 7) || (major === 24 && minor >= 15)) and returns the CJS source from the PnP loader's `load` hook instead of letting Node `fstat` the fake fd.

This repo pinned Yarn 4.12.0.

Root cause 2: missing libusb-1.0-0-dev for Node 25 source builds

node-hid@2.1.2 and usb@2.9.0 ship prebuilt binaries for older Node ABIs. On Node 25 they fall back to a make source build:

../hidapi/libusb/hid.c:47:10: fatal error: libusb.h: No such file or directory

The GitHub Actions runner doesn't have libusb-1.0-0-dev / libudev-dev pre-installed.

Changes

  1. Upgrade Yarn 4.12.0 → 4.14.1
    • .yarnrc.yml, package.json (`packageManager`), .yarn/releases/yarn-4.14.1.cjs
    • Regenerated .pnp.cjs / .pnp.loader.mjs (the loader now contains the EBADF workaround)
    • yarn.lock metadata version bumped 8 → 9 (no dependency churn)
  2. Install libusb-1.0-0-dev and libudev-dev before `yarn install` in .github/workflows/test.yml (all three jobs) and .github/workflows/coverage.yml so `node-hid` / `usb` can build from source on Node versions that lack prebuilds.

Test plan

  • node .yarn/releases/yarn-4.14.1.cjs --version → `4.14.1`
  • yarn install locally regenerates .pnp.cjs / .pnp.loader.mjs cleanly with the HAS_BROKEN_FSTAT_FOR_ZIP_FDS workaround
  • No dependency upgrades — only yarn.lock metadata version: 8 → 9
  • CI `test (25)` job's `yarn install` step completes without YN0009 errors
  • CI `test (22)`, `test (24)` no longer get canceled by the 25 failure

Note

This PR stacks cleanly on top of #1957 (evmd CI fix). Either order should work.

Made with Cursor

codingki and others added 2 commits May 1, 2026 01:55
CI was failing on the test (25) job during `yarn install` with three
build errors:

    YN0009: usb@npm:2.9.0 couldn't be built successfully
    YN0009: unrs-resolver@npm:1.11.1 couldn't be built successfully
    YN0009: node-hid@npm:2.1.2 couldn't be built successfully

Two unrelated root causes:

1. `unrs-resolver` (and any other dep loaded as CJS through Yarn PnP zip
   archives) hits "Error: EBADF: bad file descriptor, fstat" on Node
   25.7+ and Node 24.15+. Node's ESM loader started reading CJS source
   through unpatched `fs.readFileSync`, which can't `fstat` Yarn's
   zip-backed file descriptors. See
   yarnpkg/berry#7065 and
   nodejs/node#62012.

   Fixed in Yarn 4.14.1, which detects affected Node versions
   (`HAS_BROKEN_FSTAT_FOR_ZIP_FDS`) and returns the CJS source from the
   PnP loader's `load` hook instead of letting Node `fstat` the fake fd.

2. `node-hid@2.1.2` and `usb@2.9.0` ship prebuilt binaries for older
   Node versions but fall back to a libusb source build on Node 25.
   The Linux runner doesn't have `libusb-1.0-0-dev` / `libudev-dev`
   pre-installed.

Upgrade Yarn (.yarnrc.yml, package.json, .yarn/releases/, regenerated
.pnp.* + yarn.lock metadata version 9) and install the missing system
libs in `.github/workflows/test.yml` (all three jobs) and
`.github/workflows/coverage.yml`.

Made-with: Cursor
@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a broken CI pipeline by upgrading Yarn from 4.12.0 to 4.14.1 (which contains the Node ≥25.7 PnP/fstat fix) and installing libusb-1.0-0-dev / libudev-dev in all CI jobs so node-hid and usb can build from source on Node 25. The workflow and lockfile changes are clean, but two settings added to .yarnrc.yml introduce broader-than-necessary security relaxations:

  • approvedGitRepositories: \"**\" disables Yarn 4's git-dependency approval gate for all future installs.
  • enableScripts: true re-enables arbitrary postinstall execution for every dependency in the project.

Confidence Score: 3/5

The CI fix itself is correct, but two P1 security-relaxing settings in .yarnrc.yml should be scoped or justified before merging.

Two P1 findings in .yarnrc.yml — approvedGitRepositories: "**" and enableScripts: true — both remove security boundaries that Yarn 4 ships with intentionally. Neither is explained in the PR description, and both affect the entire repository going forward, not just CI. The workflow and version-bump changes are clean and low-risk.

.yarnrc.yml requires attention for the two broad security-relaxing settings added.

Security Review

  • Supply-chain gate removed (.yarnrc.yml): approvedGitRepositories: \"**\" approves all git-sourced packages without explicit review, bypassing Yarn 4's intentional prompt that guards against unreviewed git dependencies.
  • Arbitrary postinstall execution enabled (.yarnrc.yml): enableScripts: true re-enables lifecycle scripts globally; Yarn 4 ships with this disabled by default. Scoping it to specific packages (via dependenciesMeta) would reduce the blast radius.

Important Files Changed

Filename Overview
.yarnrc.yml Yarn path correctly bumped to 4.14.1, but two broad security-relaxing settings were added: approvedGitRepositories: "**" (approves all git deps without review) and enableScripts: true (enables arbitrary postinstall scripts for all packages)
.github/workflows/test.yml Adds libusb-1.0-0-dev and libudev-dev apt install step before yarn install in all three jobs; correctly placed and necessary for Node 25 source builds of node-hid/usb
.github/workflows/coverage.yml Same native dependency install step added; consistent with test.yml changes
package.json packageManager field updated from yarn@4.12.0 to yarn@4.14.1, straightforward version bump
yarn.lock Only lockfile metadata version bumped (8 → 9); no dependency changes, as stated in PR description

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CI Job Starts] --> B[actions/setup-node]
    B --> C[apt-get install libusb-1.0-0-dev libudev-dev]
    C --> D[yarn install --immutable]
    D --> E{Node version}
    E -->|Node 22/24 - prebuild exists| F[Install prebuilt node-hid / usb binary]
    E -->|Node 25 - no prebuild| G[Source build via node-gyp]
    G --> H[gcc links against libusb-1.0-0-dev ✓]
    F --> I[Build & Test]
    H --> I
    D --> J{PnP fstat issue?}
    J -->|Yarn 4.14.1 - fixed| K[PnP loader returns CJS source directly]
    J -->|Yarn 4.12.0 - broken| L[EBADF error on Node ≥25.7]
    K --> I
Loading

Reviews (1): Last reviewed commit: "[autofix.ci] apply automated fixes" | Re-trigger Greptile

Comment thread .yarnrc.yml Resolved
Comment thread .yarnrc.yml Resolved
Two settings slipped into .yarnrc.yml from a local `yarn install` run
when generating the lockfile/PnP files for the 4.14.1 upgrade:

  - approvedGitRepositories: ["**"]
  - enableScripts: true

Neither is needed:

- The repo has no git-resolved dependencies (yarn.lock has only npm
  resolutions), so approving "**" is purely a future-facing footgun
  that bypasses Yarn's git-dep review prompt.
- `enableScripts` already defaults to true in Yarn 4, so the explicit
  setting is a no-op functionally and just adds noise.

Reverting both restores the pre-existing behavior on main.

Reported by Greptile review on #1958.

Made-with: Cursor
@codingki

Copy link
Copy Markdown
Member Author

Thanks @greptile-apps. Both settings were accidental — they were auto-written into .yarnrc.yml by my local yarn install when regenerating the lockfile / PnP files for the 4.14.1 upgrade, and I missed them when staging. Reverted in e664c60:

  • approvedGitRepositories: ["**"] — dropped. The repo has no git-resolved dependencies (verified against yarn.lock), so this was purely a forward-looking footgun.
  • enableScripts: true — dropped. This is the Yarn 4 default already, so removing the explicit line is a functional no-op.

The CI pipeline still works because:

  1. Without approvedGitRepositories, Yarn would prompt only on a new git dep — none exist today.
  2. enableScripts keeps its default true, so node-hid / usb / unrs-resolver build scripts run as before.

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown

Makes sense — both explanations check out:

  • approvedGitRepositories: ["**"] is a blanket approval that would silently allow any future git dep, so dropping it is the right call. If one is ever needed, Yarn will prompt explicitly.
  • enableScripts: true is redundant since it's the Yarn 4 default; removing it keeps the config minimal without changing behavior.

The revert in e664c60 looks clean. No further concerns on this front.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@codingki
codingki merged commit 2338a7e into main Apr 30, 2026
15 checks passed
@codingki
codingki deleted the fix/yarn-node25-ebadf branch April 30, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant