diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 475f166c1e2438eba56836ef0c8f87a419fd27f6..9de54ee6a332a59e41c64d037c2e49816cbfeeb7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,7 +30,8 @@ concurrency:
 
 env:
   DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
-  NODE_VERSION: 18
+  NODE_VERSION_TEST: 18
+  NODE_VERSION_BUILD: 20
   DRY_RUN: true
   TEST_LEGACY_DECRYPTION: true
   SPARSE_CHECKOUT: |-
@@ -105,13 +106,13 @@ jobs:
         with:
           repo: ${{ github.event.repository.full_name }}
           token: ${{ github.token }}
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ env.NODE_VERSION_TEST }}
 
-      - name: Prefetch modules for `ubuntu-latest`
+      - name: Prefetch test modules for `ubuntu-latest`
         id: setup-node
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ env.NODE_VERSION_TEST }}
           os: ${{ runner.os }}
           save-cache: true
 
@@ -124,6 +125,27 @@ jobs:
         run: |
           echo "$(pnpm -s schedule-test-shards)" >> "$GITHUB_OUTPUT"
 
+  setup-build:
+    runs-on: ubuntu-latest
+
+    outputs:
+      node-version: ${{ env.NODE_VERSION_BUILD }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+        with:
+          filter: blob:none # we don't need all blobs
+          sparse-checkout: ${{ env.SPARSE_CHECKOUT }}
+          show-progress: false
+
+      - name: Prefetch build modules for `ubuntu-latest`
+        uses: ./.github/actions/setup-node
+        with:
+          node-version: ${{ env.NODE_VERSION_BUILD }}
+          os: ${{ runner.os }}
+          save-cache: true
+
   prefetch:
     needs: [setup]
 
@@ -142,7 +164,6 @@ jobs:
     strategy:
       matrix:
         os: ${{ fromJSON(needs.setup.outputs.os-matrix-prefetch) }}
-        node-version: [18]
 
     runs-on: ${{ matrix.os }}
 
@@ -161,12 +182,13 @@ jobs:
         if: needs.setup.outputs.os-matrix-is-full && runner.os != 'Linux'
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ env.NODE_VERSION_TEST }}
           os: ${{ runner.os }}
           save-cache: true
 
   lint-eslint:
-    needs: [setup]
+    needs:
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 15
 
@@ -182,7 +204,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Restore eslint cache
@@ -211,7 +233,8 @@ jobs:
           key: eslint-main-cache
 
   lint-prettier:
-    needs: [setup]
+    needs:
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 7
 
@@ -227,7 +250,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Restore prettier cache
@@ -256,7 +279,8 @@ jobs:
           key: prettier-main-cache
 
   lint-docs:
-    needs: [setup]
+    needs:
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 7
 
@@ -269,7 +293,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Lint markdown
@@ -285,7 +309,8 @@ jobs:
         run: pnpm markdown-lint
 
   lint-other:
-    needs: [setup]
+    needs:
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 7
 
@@ -298,7 +323,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Type check
@@ -337,7 +362,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ env.NODE_VERSION_TEST }}
           os: ${{ runner.os }}
 
       - name: Cache jest
@@ -348,7 +373,7 @@ jobs:
             jest-cache-${{
               runner.os
             }}-${{
-              env.NODE_VERSION
+              env.NODE_VERSION_TEST
             }}-${{
               hashFiles('pnpm-lock.yaml')
             }}-${{
@@ -416,6 +441,7 @@ jobs:
   coverage-threshold:
     needs:
       - test
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 3
     if: (success() || failure()) && github.event.pull_request.draft != true
@@ -430,7 +456,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Download coverage reports
@@ -505,7 +531,8 @@ jobs:
         run: exit 1
 
   build:
-    needs: setup
+    needs:
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 15
     if: github.event.pull_request.draft != true
@@ -518,7 +545,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Build
@@ -539,7 +566,9 @@ jobs:
           path: renovate-0.0.0-semantic-release.tgz
 
   build-docs:
-    needs: [lint-docs]
+    needs:
+      - lint-docs
+      - setup-build
     runs-on: ubuntu-latest
     timeout-minutes: 5
     if: github.event.pull_request.draft != true
@@ -552,7 +581,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - name: Build
@@ -588,7 +617,7 @@ jobs:
       - name: Setup Node.js
         uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ env.NODE_VERSION_TEST }}
 
       - name: Download package
         uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
@@ -603,7 +632,7 @@ jobs:
 
   release:
     needs:
-      - setup
+      - setup-build
       - lint-eslint
       - lint-prettier
       - lint-docs
@@ -639,7 +668,7 @@ jobs:
       - name: Setup Node.js
         uses: ./.github/actions/setup-node
         with:
-          node-version: ${{ env.NODE_VERSION }}
+          node-version: ${{ needs.setup-build.outputs.node-version }}
           os: ${{ runner.os }}
 
       - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
diff --git a/.github/workflows/update-data.yml b/.github/workflows/update-data.yml
index aead3c9df7ad32802084f873956dec70f9799c8b..faf5970d86852d8861daffb893c604fe8123ae41 100644
--- a/.github/workflows/update-data.yml
+++ b/.github/workflows/update-data.yml
@@ -5,7 +5,7 @@ on:
   workflow_dispatch:
 
 env:
-  NODE_VERSION: 18
+  NODE_VERSION: 20
 
 permissions:
   contents: read
diff --git a/package.json b/package.json
index abd071a6d20b900b7a4099494d2b791f0f0a80ca..f6f2ffbddc7e19173e47bd463646a21dc1570ec8 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
     "prepare": "run-s 'prepare:*'",
     "prepare:husky": "husky",
     "prepare:generate": "run-s 'generate:*'",
-    "prepare:re2": "node tools/check-re2.mjs",
+    "prepare:deps": "node tools/prepare-deps.mjs",
     "prestart": "run-s 'generate:*'",
     "pretest": "run-s 'generate:*'",
     "prettier": "prettier --cache --check '**/*.{ts,js,mjs,json,md,yml}'",
diff --git a/tools/check-re2.mjs b/tools/check-re2.mjs
deleted file mode 100644
index b86559956b8e6a8b2534d07471073e51c857464a..0000000000000000000000000000000000000000
--- a/tools/check-re2.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-await (async () => {
-  console.log('Checking re2 ... ');
-  try {
-    const { default: RE2 } = await import('re2');
-    new RE2('.*').exec('test');
-    console.log(`ok.`);
-  } catch (e) {
-    console.error(`error.\n${e}`);
-    process.exit(1);
-  }
-})();
diff --git a/tools/prepare-deps.mjs b/tools/prepare-deps.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..9d62b542196b1d7f34ba52897f65d87df8476ac5
--- /dev/null
+++ b/tools/prepare-deps.mjs
@@ -0,0 +1,65 @@
+import { execSync } from 'child_process';
+
+function testRe2() {
+  execSync(
+    `node -e "try{require('re2')('.*').exec('test')}catch(e){console.error(e);if(e.code === 'ERR_DLOPEN_FAILED' && e.message.includes('NODE_MODULE_VERSION')) process.exit(1); else process.exit(-1)}"`,
+    { stdio: 'inherit' },
+  );
+  console.log(`Ok.`);
+}
+
+function testSqlite() {
+  execSync(
+    `node -e "try{new require('better-sqlite3')(':memory:')}catch(e){console.error(e);if(e.code === 'ERR_DLOPEN_FAILED' && e.message.includes('NODE_MODULE_VERSION')) process.exit(1); else process.exit(-1)}"`,
+    { stdio: 'inherit' },
+  );
+  console.log(`Ok.`);
+}
+
+(() => {
+  console.log('Checking re2 ... ');
+  try {
+    testRe2();
+  } catch (e) {
+    console.error(`Failed.\n${e}`);
+    try {
+      if (e.status === 1) {
+        console.log(`Retry re2 install ...`);
+        execSync('pnpm run install', {
+          stdio: 'inherit',
+          cwd: `${process.cwd()}/node_modules/re2`,
+        });
+        testRe2();
+        return;
+      }
+    } catch (e1) {
+      console.error(`Retry failed.\n${e1}`);
+    }
+
+    process.exit(1);
+  }
+})();
+
+(() => {
+  console.log('Checking better-sqlite3 ... ');
+  try {
+    testSqlite();
+  } catch (e) {
+    console.error(`Failed.\n${e}`);
+    try {
+      if (e.status === 1) {
+        console.log(`Retry better-sqlite3 install ...`);
+        execSync('pnpm run install', {
+          stdio: 'inherit',
+          cwd: `${process.cwd()}/node_modules/better-sqlite3`,
+        });
+        testSqlite();
+        return;
+      }
+    } catch (e1) {
+      console.error(`Retry failed.\n${e1}`);
+    }
+
+    process.exit(1);
+  }
+})();