I have already write an update post about bitwarden backup. After running smoothly for months, my automated Bitwarden backup GitHub Action suddenly started failing. The error message was cryptic:
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/peterbabic/backups/bitwarden/node_modules/open/index.js
from /Users/peterbabic/backups/bitwarden/node_modules/@bitwarden/cli/build/bw.js not supported.
Additionally, I was seeing warnings about Node.js engine compatibility:
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: '@koa/[email protected]',
npm warn EBADENGINE required: { node: '>= 20' },
npm warn EBADENGINE current: { node: 'v18.20.8', npm: '10.8.2' }
npm warn EBADENGINE }
Understanding the Root Causes#
There were actually two separate issues at play:
Issue 1: CommonJS vs ESM Compatibility#
The @bitwarden/cli npm package depends on the open package. Recently,
the open package was updated to version 10.x, which is ESM-only. However,
the Bitwarden CLI is built using CommonJS and tries to use require() to
import it, which causes the ERR_REQUIRE_ESM error.
The open package became ESM-only starting from version 9.0.0. The last
CommonJS-compatible version was 8.4.2.
Issue 2: Node.js Version Requirements#
Several dependencies of @bitwarden/cli were recently updated to require
Node.js 20+:
@koa/[email protected]requires Node >= 20[email protected]requires Node >= 20
My GitHub Actions workflow was using actions/setup-node@v4 without
specifying a version, which defaulted to Node 18.x.
Solution 1: The Quick Fix (npm overrides)#
The first solution was to use npm's overrides feature to force the use of
an older, CommonJS-compatible version of the open package.
I updated package.json:
{
"dependencies": {
"@bitwarden/cli": "^2025.10.0"
},
"overrides": {
"open": "8.4.2"
}
}
And specified Node 20 in the workflow:
- uses: actions/setup-node@v4
with:
node-version: "20"
After running npm install, the override was applied:
└─┬ @bitwarden/[email protected]
└── [email protected] overridden
This fixed both issues, and the workflow started working again.
Solution 2: The Better Fix (Using Official Binary)#
While the override approach worked, it felt like a hack. A cleaner solution is to use the official Bitwarden CLI binary instead of installing it via npm. This is actually the recommended approach for CI/CD environments.
I completely rewrote the workflow to download the official binary:
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: '20'
- - run: |
- npm install
- ./node_modules/@bitwarden/cli/build/bw.js login --apikey
- ./node_modules/@bitwarden/cli/build/bw.js export --format json --session $(./node_modules/@bitwarden/cli/build/bw.js unlock --passwordenv BW_PASSWORD --raw) --raw | openssl aes-256-cbc -a -salt -pbkdf2 -k $BW_PASSWORD -out personal.json.enc
- ./node_modules/@bitwarden/cli/build/bw.js export --format json --session $(./node_modules/@bitwarden/cli/build/bw.js unlock --passwordenv BW_PASSWORD --raw) --raw --organizationid "$BW_ORGANIZATION_ID" | openssl aes-256-cbc -a -salt -pbkdf2 -k "$BW_PASSWORD" -out organization.json.enc
- ./node_modules/@bitwarden/cli/build/bw.js lock
- ./node_modules/@bitwarden/cli/build/bw.js logout
+
+ - name: Install Bitwarden CLI
+ run: |
+ curl -L "https://vault.bitwarden.com/download/?app=cli&platform=linux" -o bw.zip
+ unzip bw.zip
+ chmod +x bw
+ sudo mv bw /usr/local/bin/
+
+ - name: Backup Bitwarden
+ run: |
+ bw login --apikey
+ bw export --format json --session $(bw unlock --passwordenv BW_PASSWORD --raw) --raw | openssl aes-256-cbc -a -salt -pbkdf2 -k $BW_PASSWORD -out personal.json.enc
+ bw export --format json --session $(bw unlock --passwordenv BW_PASSWORD --raw) --raw --organizationid "$BW_ORGANIZATION_ID" | openssl aes-256-cbc -a -salt -pbkdf2 -k "$BW_PASSWORD" -out organization.json.enc
+ bw lock
+ bw logout
I then removed the npm-related files entirely:
rm package.json package-lock.json
Here's the complete resulting workflow file:
name: Bitwarden.com backup
on:
workflow_dispatch:
schedule:
- cron: "30 0 * * 0"
jobs:
backup:
runs-on: ubuntu-22.04
env:
BW_CLIENTID: ${{ secrets.BW_CLIENTID }}
BW_CLIENTSECRET: ${{ secrets.BW_CLIENTSECRET }}
BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
steps:
- uses: actions/checkout@v4
- name: Install Bitwarden CLI
run: |
curl -L "https://vault.bitwarden.com/download/?app=cli&platform=linux" -o bw.zip
unzip bw.zip
chmod +x bw
sudo mv bw /usr/local/bin/
- name: Backup Bitwarden
run: |
bw login --apikey
bw export --format json --session $(bw unlock --passwordenv BW_PASSWORD --raw) --raw | openssl aes-256-cbc -a -salt -pbkdf2 -k $BW_PASSWORD -out personal.json.enc
bw export --format json --session $(bw unlock --passwordenv BW_PASSWORD --raw) --raw --organizationid "$BW_ORGANIZATION_ID" | openssl aes-256-cbc -a -salt -pbkdf2 -k "$BW_PASSWORD" -out organization.json.enc
bw lock
bw logout
- if: ${{ !env.ACT }}
uses: stefanzweifel/git-auto-commit-action@v5
Benefits of Using the Official Binary#
- No dependency management: No npm packages to manage or version conflicts to worry about
- No CommonJS/ESM issues: The binary has no transitive dependency problems
- No Node.js version constraints: Don't need to install Node.js at all
- Faster CI runs: Downloading and extracting a binary is faster than
npm install - Simpler commands: Just
bwinstead of./node_modules/@bitwarden/cli/build/bw.js - More reliable: Official binary is the recommended approach by Bitwarden
Testing Locally with act#
If you use act to test GitHub Actions locally, you can test the workflow
with:
act workflow_dispatch
Note: the
.secretsfile with the actual secrets must be provided to properly run this locally. Keeping this file locally however poses quite a security risk, so it is better to test this only on GitHub where the environment is a little bit more secure.
Conclusion#
When dependencies start causing compatibility issues, sometimes the best solution is to step back and ask: "Do I even need these dependencies?" In this case, switching from the npm package to the official binary eliminated all the problems and resulted in a cleaner, more maintainable solution.
If you're using Bitwarden CLI in your CI/CD pipelines, I highly recommend using the official binary instead of the npm package to avoid similar issues in the future. Enjoy!