diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4286e48c24f03172174464be5afbda5fd4fd36df..bb59222df7d540e2bbf8eda3185e27967f7f39ec 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,7 +29,44 @@ env:
   DRY_RUN: true
 
 jobs:
+  setup:
+    runs-on: ubuntu-latest
+
+    outputs:
+      os-matrix: ${{ steps.os-matrix.outputs.os-matrix }}
+
+    env:
+      # Field required for GitHub CLI
+      GH_REPO: ${{ github.event.repository.full_name }}
+      GH_TOKEN: ${{ github.token }}
+
+      # Pull Request data may present or it may not
+      PR: ${{ github.event.pull_request.number }}
+      PR_LABELS: '[]'
+
+    steps:
+      - name: Fetch PR data
+        if: ${{ env.PR }}
+        env:
+          PR_URL: https://api.github.com/repos/{owner}/{repo}/pulls/${{ env.PR }}
+          JQ_FILTER: >-
+            "PR_LABELS=" + ([.labels[].name] | tostring)
+        run: gh api ${{ env.PR_URL }} | jq -rc '${{ env.JQ_FILTER }}' >> "$GITHUB_ENV"
+
+      - name: Detect OS matrix
+        id: os-matrix
+        env:
+          CI_FULLTEST: >-
+            ${{ contains(fromJSON(env.PR_LABELS), 'ci:fulltest') && 'true' || '' }}
+          OS_ALL: '["ubuntu-latest", "macos-latest", "windows-latest"]'
+          OS_LINUX_ONLY: '["ubuntu-latest"]'
+        run: >-
+          echo 'os-matrix=${{
+            (!env.PR || env.CI_FULLTEST) && env.OS_ALL || env.OS_LINUX_ONLY
+          }}' >> "$GITHUB_OUTPUT"
+
   test:
+    needs: setup
     name: ${{ matrix.node-version == 18 && format('test ({0})', matrix.os) || format('test ({0}, node-{1})', matrix.os, matrix.node-version) }}
     runs-on: ${{ matrix.os }}
 
@@ -38,17 +75,8 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest]
+        os: ${{ fromJSON(needs.setup.outputs.os-matrix) }}
         node-version: [18]
-        # skip macOS and Windows test on pull requests without 'ci:fulltest' label
-        include: >-
-          ${{ fromJSON((github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:fulltest')) && '[{
-            "os": "macos-latest",
-            "node-version": 18
-          }, {
-            "os": "windows-latest",
-            "node-version": 18
-          }]' || '[]') }}
 
     env:
       coverage: ${{ matrix.os == 'ubuntu-latest' && matrix.node-version == 18 }}