From de1cf0fac78e6d60ec6d59b4c379f53dee285490 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 6 Aug 2026 04:57:30 +0000 Subject: [PATCH] fix(ci): retry the Windows build and skip the redundant test-step rebuild MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit unit-test / test (windows-latest, *) has failed repeatedly on main (37% of the last 40 CI push runs) with STATUS_DLL_INIT_FAILED (exit -1073741502 / 3221225794) crashing turbo's native build toolchain (rolldown, via tsdown/vite) mid-build — an environment fault unrelated to the code under test, never seen on ubuntu. - scripts/ci-retry.ts wraps a command with bounded retries and surfaces the real exit code once the budget is spent, so a genuine compile error still fails the run. - package.json: pnpm run ci:build runs the existing build through it. - .github/workflows/ci.yml: unit-test's Build step now runs ci:build; its Unit Test step runs plain vitest instead of build && vitest, since the Build step already produced a fresh dist/ for the same checkout — halving the number of full-monorepo native builds per job and with it the crash's attack surface. --- .github/workflows/ci.yml | 7 ++++++ package.json | 1 + scripts/ci-retry.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 scripts/ci-retry.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c09d32d..c6cfad0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,13 @@ jobs: unit-test: uses: sxzz/workflows/.github/workflows/unit-test.yml@main with: + build: pnpm run ci:build + # The Build step above already produced a fresh dist/ for this exact + # checkout, so skip `test`'s own `build && vitest` — running plain + # vitest halves the number of full-monorepo `turbo run build` passes + # per job, which is where the flaky Windows native-toolchain crash + # (see scripts/ci-retry.ts) shows up. + test: pnpm exec vitest lint: pnpm run lint && pnpm run knip e2e: diff --git a/package.json b/package.json index 71514b55..5fdb4af9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bugs": "https://github.com/devframes/devframe/issues", "scripts": { "build": "turbo run build --concurrency=3", + "ci:build": "tsx scripts/ci-retry.ts pnpm run build", "watch": "pnpm -r run watch", "play": "tsx scripts/play.ts", "dev": "tsx scripts/play.ts", diff --git a/scripts/ci-retry.ts b/scripts/ci-retry.ts new file mode 100644 index 00000000..6edc094d --- /dev/null +++ b/scripts/ci-retry.ts @@ -0,0 +1,50 @@ +import { spawnSync } from 'node:child_process' +import process from 'node:process' + +/** + * Windows CI runners intermittently crash while spawning devframe's native + * build toolchain (rolldown, via tsdown/vite) with + * `STATUS_DLL_INIT_FAILED` (exit code -1073741502, surfaced by pnpm/turbo + * as 3221225794) under `turbo run build`'s concurrency — an environment + * fault unrelated to the code under test. `unit-test / test + * (windows-latest, *)` in the "CI" workflow has failed on this signature + * across many unrelated commits and packages. + * + * Retries the given command a bounded number of times instead of failing + * the run on this class of transient crash. A genuine compile error fails + * the same way on every attempt, so it still surfaces after the retry + * budget is spent. + */ + +const [, , ...commandParts] = process.argv +if (commandParts.length === 0) { + console.error('Usage: tsx scripts/ci-retry.ts ') + process.exit(1) +} + +const command = commandParts.join(' ') +const attempts = Number(process.env.CI_RETRY_ATTEMPTS ?? 3) +const delayMs = Number(process.env.CI_RETRY_DELAY_MS ?? 5000) + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +async function main(): Promise { + for (let attempt = 1; attempt <= attempts; attempt++) { + const result = spawnSync(command, { stdio: 'inherit', shell: true }) + if (result.status === 0) + return + + const code = result.status ?? result.signal ?? 'unknown' + const isLastAttempt = attempt === attempts + console.error(`\n[ci-retry] \`${command}\` failed (exit ${code}), attempt ${attempt}/${attempts}${isLastAttempt ? '' : ` — retrying in ${delayMs}ms`}\n`) + + if (isLastAttempt) + process.exit(typeof result.status === 'number' ? result.status : 1) + + await sleep(delayMs) + } +} + +main()