From d747533733dc738adafc7999b7f5aaa2bc6426e5 Mon Sep 17 00:00:00 2001 From: moosebay Date: Sun, 9 Aug 2026 16:48:07 +0300 Subject: [PATCH 1/4] fix(ci): fail release runs when a platform build fails Both release workflows set `continue-on-error: true` on the matrix build job, which forces every row's conclusion to success even when the build dies. cli@1.1.0 shipped that way: the two Windows rows failed on an unexpanded `${PLATFORM}`, published nothing, and the run still reported a green check, so the release sat with 4 of its 6 assets for hours. Drop it, and add `fail-fast: false` in the same move. fail-fast is what keeps the rows independent once continue-on-error is gone -- without it the first failing target would cancel the healthy rows still in flight, including the ~20 minute darwin-x64 build, and strand the release half-published. The result is the behaviour that was intended all along: every platform still gets its chance to build, and a platform that cannot produce an asset now fails the run that publishes it. --- .github/workflows/release-electron-builder.yaml | 6 +++++- .github/workflows/release-go.yaml | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-electron-builder.yaml b/.github/workflows/release-electron-builder.yaml index a01ce233f..51147cdc8 100644 --- a/.github/workflows/release-electron-builder.yaml +++ b/.github/workflows/release-electron-builder.yaml @@ -6,8 +6,12 @@ on: jobs: build: name: Build - continue-on-error: true + # See release-go.yaml for the full rationale. Same defect, same fix: a + # masked failure here silently ships a desktop release missing an installer + # for whichever platform broke. fail-fast: false keeps the rows independent + # so one failure cannot cancel the others (notarization alone runs long). strategy: + fail-fast: false matrix: runner: - macos-15-intel # x64 diff --git a/.github/workflows/release-go.yaml b/.github/workflows/release-go.yaml index fc9b8ddb2..caf9d191d 100644 --- a/.github/workflows/release-go.yaml +++ b/.github/workflows/release-go.yaml @@ -6,8 +6,18 @@ on: jobs: build: name: Build Go Binary - continue-on-error: true + # Deliberately no `continue-on-error: true`. It used to be set here, which + # forced every row's conclusion to success even when the build died, so + # cli@1.1.0 published 4 of its 6 assets behind a green check and the two + # broken Windows rows went unnoticed until someone opened the logs by hand. + # A release that cannot produce an asset must fail the run that publishes it. + # + # fail-fast: false preserves the one useful thing continue-on-error was + # doing by accident: rows stay independent. Without it, the first failing + # target would cancel the five healthy ones still in flight — including the + # ~20 minute darwin-x64 build — and strand the release half-published. strategy: + fail-fast: false matrix: include: - runner: macos-15-intel From c527283dc8aa7c733bc27f1c8767b6a572406e6c Mon Sep 17 00:00:00 2001 From: moosebay Date: Sun, 9 Aug 2026 16:48:25 +0300 Subject: [PATCH 2/4] fix(ci): stop ESLint dying on the Tailwind synckit worker `ui:lint` fails intermittently on main with Internal error: Atomics.wait() failed: timed-out Rule: "better-tailwindcss/enforce-canonical-classes" eslint-plugin-better-tailwindcss resolves the Tailwind design system in a synckit worker and blocks on Atomics.wait(). Building that context for packages/ui/src/styles/index.css is a cold, CPU-bound compile, and the plugin caps the wait at 30s. On a 4-vCPU runner with several lint targets in flight that ceiling gets crossed and ESLint aborts outright -- a hard crash, not a lint finding, so `task lint` fails and --nxBail skips the rest of the run. 4.3.2 passes `timeout: 30000` explicitly to createSyncFn, which overrides synckit's own default, so SYNCKIT_TIMEOUT was silently ignored and there was no way to raise it. 4.4.0 is the first release that reads the env var; its recommended preset is otherwise unchanged (the two rules it adds are both `recommended: false`), so the effective rule set does not move. Raise the ceiling to 120s for lint targets. It is a deadline, not a budget: a healthy run never waits any longer than it does today. --- nx.json | 16 +++++++- pnpm-lock.yaml | 90 +++++++++++++++++++++++++++++++-------------- pnpm-workspace.yaml | 6 ++- 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/nx.json b/nx.json index c289b6092..e809eddc2 100644 --- a/nx.json +++ b/nx.json @@ -23,7 +23,21 @@ }, "lint": { - "dependsOn": ["^pre-lint", "pre-lint"] + "dependsOn": ["^pre-lint", "pre-lint"], + + // eslint-plugin-better-tailwindcss resolves the Tailwind design system in a + // synckit worker and blocks on Atomics.wait(). Building that context for + // packages/ui/src/styles/index.css is a cold, CPU-bound compile that the + // plugin caps at 30s. On a 4-vCPU CI runner with several lint targets in + // flight it overran the cap and ESLint died with + // Internal error: Atomics.wait() failed: timed-out + // failing main at random. Raise the ceiling; it is a deadline, not a budget, + // so a healthy run never waits any longer than it does today. + "options": { + "env": { + "SYNCKIT_TIMEOUT": "120000" + } + } }, "test": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18913f79c..db1fc3b70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -250,8 +250,8 @@ catalogs: specifier: 4.4.4 version: 4.4.4 eslint-plugin-better-tailwindcss: - specifier: 4.3.2 - version: 4.3.2 + specifier: 4.4.0 + version: 4.4.0 eslint-plugin-import-x: specifier: 4.16.1 version: 4.16.1 @@ -1071,7 +1071,7 @@ importers: version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-better-tailwindcss: specifier: 'catalog:' - version: 4.3.2(eslint@9.39.2(jiti@2.6.1))(tailwindcss@4.2.1)(typescript@5.9.3) + version: 4.4.0(eslint@9.39.2(jiti@2.6.1))(tailwindcss@4.2.1)(typescript@5.9.3) eslint-plugin-import-x: specifier: 'catalog:' version: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) @@ -2650,9 +2650,9 @@ packages: resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@3.6.9': - resolution: {integrity: sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + '@eslint/css-tree@4.0.5': + resolution: {integrity: sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/eslintrc@3.3.4': resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==} @@ -5443,10 +5443,10 @@ packages: cpu: [x64] os: [win32] - '@valibot/to-json-schema@1.5.0': - resolution: {integrity: sha512-GE7DmSr1C2UCWPiV0upRH6mv0cCPsqYGs819fb6srCS1tWhyXrkGGe+zxUiwzn/L1BOfADH4sNjY/YHCuP8phQ==} + '@valibot/to-json-schema@1.7.1': + resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} peerDependencies: - valibot: ^1.2.0 + valibot: ^1.4.0 '@vitejs/plugin-react@5.1.4': resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==} @@ -6981,6 +6981,10 @@ packages: resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + engines: {node: '>=10.13.0'} + enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} engines: {node: '>=8.6'} @@ -7105,8 +7109,8 @@ packages: eslint-plugin-import-x: optional: true - eslint-plugin-better-tailwindcss@4.3.2: - resolution: {integrity: sha512-1DLX2QmHmOj3u667f8vEI0zKoRc0Y1qJt33tfIeIkpTyzWaz9b2GzWBLD4bR+WJ/kxzC0Skcbx7cMerRWQ6OYg==} + eslint-plugin-better-tailwindcss@4.4.0: + resolution: {integrity: sha512-OVFcRnyFOMJR7dFhlfNbIBlM7D4o1g1yBGDCWvsJWliXFE4c4U06CEZ0pcFatHa5g7PAwiDRAd57gxxJT/7EIQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=23.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -8491,8 +8495,8 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.23.0: - resolution: {integrity: sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ==} + mdn-data@2.29.0: + resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -10171,9 +10175,14 @@ packages: engines: {node: '>=18.18.0'} hasBin: true - tailwind-csstree@0.1.4: - resolution: {integrity: sha512-FzD187HuFIZEyeR7Xy6sJbJll2d4SybS90satC8SKIuaNRC05CxMvdzN7BUsfDQffcnabckRM5OIcfArjsZ0mg==} + tailwind-csstree@0.3.3: + resolution: {integrity: sha512-je9J5UYRsTJqAjYrIBMMlge8T/rreRd44pJxgG5Zx/zeo4kAC/liUKqzztRZrGlYRJLvIf2Cb1DVJMTXSzEShA==} engines: {node: '>=18.18'} + peerDependencies: + '@eslint/css': '>=1.0.0' + peerDependenciesMeta: + '@eslint/css': + optional: true tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -10200,6 +10209,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -10653,6 +10666,14 @@ packages: typescript: optional: true + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + validate-html-nesting@1.2.4: resolution: {integrity: sha512-doQi7e8EJ2OWneSG1aZpJluS6A49aZM0+EICXWKm1i6WvqTLmq0tpUcImc4KTWG50mORO0C4YDBtOCSYvElftw==} @@ -12770,9 +12791,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@3.6.9': + '@eslint/css-tree@4.0.5': dependencies: - mdn-data: 2.23.0 + mdn-data: 2.29.0 source-map-js: 1.2.1 '@eslint/eslintrc@3.3.4': @@ -16446,9 +16467,9 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3))': + '@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@5.9.3))': dependencies: - valibot: 1.2.0(typescript@5.9.3) + valibot: 1.4.2(typescript@5.9.3) '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -18127,6 +18148,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + enhanced-resolve@5.24.5: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + enquirer@2.3.6: dependencies: ansi-colors: 4.1.3 @@ -18350,20 +18376,21 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-better-tailwindcss@4.3.2(eslint@9.39.2(jiti@2.6.1))(tailwindcss@4.2.1)(typescript@5.9.3): + eslint-plugin-better-tailwindcss@4.4.0(eslint@9.39.2(jiti@2.6.1))(tailwindcss@4.2.1)(typescript@5.9.3): dependencies: - '@eslint/css-tree': 3.6.9 - '@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3)) - enhanced-resolve: 5.20.0 + '@eslint/css-tree': 4.0.5 + '@valibot/to-json-schema': 1.7.1(valibot@1.4.2(typescript@5.9.3)) + enhanced-resolve: 5.24.5 jiti: 2.6.1 synckit: 0.11.12 - tailwind-csstree: 0.1.4 + tailwind-csstree: 0.3.3 tailwindcss: 4.2.1 tsconfig-paths-webpack-plugin: 4.2.0 - valibot: 1.2.0(typescript@5.9.3) + valibot: 1.4.2(typescript@5.9.3) optionalDependencies: eslint: 9.39.2(jiti@2.6.1) transitivePeerDependencies: + - '@eslint/css' - typescript eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): @@ -19987,7 +20014,7 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.23.0: {} + mdn-data@2.29.0: {} media-typer@0.3.0: {} @@ -22116,7 +22143,7 @@ snapshots: transitivePeerDependencies: - typescript - tailwind-csstree@0.1.4: {} + tailwind-csstree@0.3.3: {} tailwind-merge@3.5.0: {} @@ -22134,6 +22161,8 @@ snapshots: tapable@2.3.0: {} + tapable@2.3.3: {} + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -22340,7 +22369,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.20.0 + enhanced-resolve: 5.24.5 tapable: 2.3.0 tsconfig-paths: 4.2.0 @@ -22641,6 +22670,11 @@ snapshots: valibot@1.2.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 + optional: true + + valibot@1.4.2(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 validate-html-nesting@1.2.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a02f62536..2b5a0cbb3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -90,7 +90,11 @@ catalog: eslint: 9.39.2 eslint-config-prettier: 10.1.8 eslint-import-resolver-typescript: 4.4.4 - eslint-plugin-better-tailwindcss: 4.3.2 + # 4.4.0 is the first release whose synckit worker honours SYNCKIT_TIMEOUT + # (4.3.2 hardcoded 30s and passed it explicitly to createSyncFn, which + # overrode synckit's own default, so the env var was silently ignored). + # nx.json raises that timeout for lint targets — see the note there. + eslint-plugin-better-tailwindcss: 4.4.0 eslint-plugin-import-x: 4.16.1 eslint-plugin-jsx-a11y: 6.10.2 eslint-plugin-perfectionist: 5.6.0 From 69e24a0323e09599b741f69fa4fd68f83c0d4115 Mon Sep 17 00:00:00 2001 From: moosebay Date: Sun, 9 Aug 2026 16:48:35 +0300 Subject: [PATCH 3/4] fix(ci): repair benchmark comment guard and workflow metadata Clears every remaining actionlint finding across .github. The benchmark PR comment job guarded on `github.com.event_name == 'pull_request'`. There is no `com` property on the github context, so the expression evaluates to null, never equals 'pull_request', and the job has silently never run. Use `github.event_name`. sql.yml still pinned actions/checkout@v3, whose runner GitHub no longer supports; every other workflow is already on v4. The three composite actions carried empty `description` fields, which actionlint reports against each workflow that uses them. --- .github/actions/dependencies-unix/action.yaml | 2 +- .github/actions/dependencies-windows/action.yaml | 2 +- .github/actions/setup/action.yaml | 2 +- .github/workflows/benchmark-generic.yml | 2 +- .github/workflows/sql.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/dependencies-unix/action.yaml b/.github/actions/dependencies-unix/action.yaml index 004ba59b8..a7dd7b1e6 100644 --- a/.github/actions/dependencies-unix/action.yaml +++ b/.github/actions/dependencies-unix/action.yaml @@ -1,5 +1,5 @@ name: Setup Unix dependencies -description: '' +description: Install the Nix toolchain and expose the flake devshell to later steps. runs: using: composite diff --git a/.github/actions/dependencies-windows/action.yaml b/.github/actions/dependencies-windows/action.yaml index 0782f6529..362e13730 100644 --- a/.github/actions/dependencies-windows/action.yaml +++ b/.github/actions/dependencies-windows/action.yaml @@ -1,5 +1,5 @@ name: Setup Windows dependencies -description: '' +description: Install the Scoop build toolchain and AzureSignTool, and pin pnpm to the version the Nix flake uses elsewhere. runs: using: composite diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index c88da7def..6b202b57b 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -1,5 +1,5 @@ name: Setup runner -description: '' +description: Install the platform toolchain, Node and Go, then restore workspace and Go tool dependencies. inputs: shell: diff --git a/.github/workflows/benchmark-generic.yml b/.github/workflows/benchmark-generic.yml index 3a85a3ada..a27652e65 100644 --- a/.github/workflows/benchmark-generic.yml +++ b/.github/workflows/benchmark-generic.yml @@ -191,7 +191,7 @@ jobs: always() && needs.benchmark.result == 'success' && needs.benchmark.outputs.has-changes == 'true' && - github.com.event_name == 'pull_request' + github.event_name == 'pull_request' steps: - name: Find and update PR comment uses: actions/github-script@v7 diff --git a/.github/workflows/sql.yml b/.github/workflows/sql.yml index f4e8f7f77..fb0e44a83 100644 --- a/.github/workflows/sql.yml +++ b/.github/workflows/sql.yml @@ -10,7 +10,7 @@ jobs: name: SQL Vet runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: sqlc-dev/setup-sqlc@v4 with: sqlc-version: '1.30.0' From 197acbc4f1b45cf1e7bc98e8d94c31ab62187abe Mon Sep 17 00:00:00 2001 From: moosebay Date: Sun, 9 Aug 2026 16:53:11 +0300 Subject: [PATCH 4/4] fix(ci): grant the benchmark comment job the token it now needs Repairing the `github.event_name` guard makes this job execute for the first time. The workflow declares no `permissions:` block and the repository default for GITHUB_TOKEN is read-only, so the job would have run straight into a 403 on issues.createComment -- trading a silently skipped job for a visibly failing one. Grant `pull-requests: write` at the job level, which is all it needs: its only step reads and writes PR comments, and it never checks out the repo. --- .github/workflows/benchmark-generic.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/benchmark-generic.yml b/.github/workflows/benchmark-generic.yml index a27652e65..02b82f098 100644 --- a/.github/workflows/benchmark-generic.yml +++ b/.github/workflows/benchmark-generic.yml @@ -187,6 +187,12 @@ jobs: name: Post Results Comment runs-on: ubuntu-latest needs: benchmark + # This job never actually ran until the `github.event_name` guard below was + # repaired, so it never needed a token that could write. The repository + # default is read-only, which would now turn every benchmarked PR into a 403 + # on issues.createComment. The job only reads and writes PR comments. + permissions: + pull-requests: write if: | always() && needs.benchmark.result == 'success' &&