From bb87284a94f04db1dd4e7693f8339ae57b7613ea Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 10 Nov 2026 21:35:39 -0500 Subject: [PATCH] chore(ci): keep the heavy test tier off pull requests pull requests now run the affected unit tests on linux only. the windows unit tests, the compiled builds and the four playwright suites still run on every push to dev and beta, on nightly runs, and as a release.yml gate before a stable release publishes. --- .github/workflows/check-test.yml | 94 ++++++++++++++++---------------- .github/workflows/release.yml | 36 ++++++++---- 2 files changed, 73 insertions(+), 57 deletions(-) diff --git a/.github/workflows/check-test.yml b/.github/workflows/check-test.yml index 71e1b4f83ed..da054adff7f 100644 --- a/.github/workflows/check-test.yml +++ b/.github/workflows/check-test.yml @@ -1,5 +1,8 @@ name: check-test +# Pull requests run the fast tier: the affected unit tests on Linux. The full tier — unit tests on Windows, the +# compiled builds, and the Playwright suites on both OSes — runs on pushes to dev and beta, on nightly and manual +# runs, and as the gate release.yml calls before it publishes a stable release. on: push: branches: @@ -13,6 +16,12 @@ on: description: Run only the quarantined browser tests (packages/app/e2e/QUARANTINE.md) type: boolean default: false + workflow_call: + inputs: + quarantine: + description: Run only the quarantined browser tests (packages/app/e2e/QUARANTINE.md) + type: boolean + default: false concurrency: # A new push to a pull request cancels its stale run. Every push and manual run keeps its own group, so each @@ -26,11 +35,18 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # Push and pull request runs test only what changed. Manual runs and the release gate test everything: they + # have no base commit to diff against. + AFFECTED: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} + # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. + TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} + TURBO_SCM_HEAD: ${{ github.sha }} jobs: + # Only the browser suites need this, and pull requests run no browser suites. affected: name: affected packages - if: ${{ !github.event.pull_request.draft }} + if: ${{ !github.event.pull_request.draft && github.event_name != 'pull_request' }} runs-on: blacksmith-4vcpu-ubuntu-2404 outputs: e2e: ${{ steps.packages.outputs.app }} @@ -39,8 +55,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} - # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -49,11 +64,8 @@ jobs: - name: Find affected packages id: packages - env: - TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} - TURBO_SCM_HEAD: ${{ github.sha }} run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "$AFFECTED" != "true" ]; then echo "app=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -67,11 +79,8 @@ jobs: strategy: fail-fast: false matrix: - settings: - - name: linux - host: blacksmith-4vcpu-ubuntu-2404 - - name: windows - host: blacksmith-4vcpu-windows-2025 + # Pull requests run Linux only; Windows runs the same tests on pushes, nightly runs and the release gate. + settings: ${{ fromJSON(github.event_name == 'pull_request' && '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"}]' || '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"},{"name":"windows","host":"blacksmith-4vcpu-windows-2025"}]') }} runs-on: ${{ matrix.settings.host }} defaults: run: @@ -109,15 +118,13 @@ jobs: timeout-minutes: 20 run: | # The runners have four vCPUs, and each Bun test process performs its own concurrent work. - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - GITHUB_ACTIONS=false bun turbo test --concurrency=3 + if [ "$AFFECTED" = "true" ]; then + GITHUB_ACTIONS=false bun turbo test --affected --concurrency=3 exit 0 fi - GITHUB_ACTIONS=false bun turbo test --affected --concurrency=3 + GITHUB_ACTIONS=false bun turbo test --concurrency=3 env: OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} - TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} - TURBO_SCM_HEAD: ${{ github.sha }} - name: Verify packed codemode package if: runner.os == 'Linux' @@ -128,14 +135,11 @@ jobs: if: runner.os == 'Linux' timeout-minutes: 15 run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - bun turbo verify:package --filter=@opencode/sdk + if [ "$AFFECTED" = "true" ]; then + bun turbo verify:package --affected --filter=@opencode/sdk exit 0 fi - bun turbo verify:package --affected --filter=@opencode/sdk - env: - TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} - TURBO_SCM_HEAD: ${{ github.sha }} + bun turbo verify:package --filter=@opencode/sdk - name: Check generated client if: runner.os == 'Linux' @@ -149,7 +153,8 @@ jobs: build: name: build (${{ matrix.settings.name }}) - if: ${{ !inputs.quarantine && !github.event.pull_request.draft }} + # Compiling the CLI and smoke-testing its service lifecycle is part of the full tier. + if: ${{ !inputs.quarantine && github.event_name != 'pull_request' && !github.event.pull_request.draft }} strategy: fail-fast: false matrix: @@ -167,8 +172,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} - # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + fetch-depth: 0 - name: Setup Bun uses: ./.github/actions/setup-bun @@ -189,14 +193,11 @@ jobs: if: ${{ !cancelled() && runner.os == 'Linux' }} timeout-minutes: 10 run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - bun turbo build --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' + if [ "$AFFECTED" = "true" ]; then + bun turbo build --affected --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' exit 0 fi - bun turbo build --affected --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' - env: - TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} - TURBO_SCM_HEAD: ${{ github.sha }} + bun turbo build --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' - name: Setup Node build runtime if: ${{ !cancelled() }} @@ -223,8 +224,12 @@ jobs: strategy: fail-fast: false matrix: - # Beta PRs gate on Linux; Windows runs on pushes and manual runs (nightly-beta.yml dispatches beta). - settings: ${{ fromJSON(github.base_ref == 'beta' && '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"}]' || '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"},{"name":"windows","host":"blacksmith-4vcpu-windows-2025"}]') }} + # Browser suites only run on pushes, nightly runs and the release gate, so every run covers both OSes. + settings: + - name: linux + host: blacksmith-4vcpu-ubuntu-2404 + - name: windows + host: blacksmith-4vcpu-windows-2025 quarantine: ${{ fromJSON(inputs.quarantine && '[true]' || '[false]') }} # Playwright splits whole spec files across shards by test count, not duration. Every app-N # entry must use the same total, and the entries must cover 1..total. The service worker suite @@ -242,7 +247,7 @@ jobs: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers # Beta only (packages/app/e2e/QUARANTINE.md): gate legs skip quarantined tests, retry once and stop after 8 # failures; quarantine legs run only those tests, to the end. Other branches keep the config defaults. - PLAYWRIGHT_ARGS: ${{ matrix.quarantine && '--grep @quarantine --pass-with-no-tests --retries=1' || (github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && '--grep-invert @quarantine --max-failures=8 --retries=1' || '' }} + PLAYWRIGHT_ARGS: ${{ matrix.quarantine && '--grep @quarantine --pass-with-no-tests --retries=1' || github.ref == 'refs/heads/beta' && '--grep-invert @quarantine --max-failures=8 --retries=1' || '' }} defaults: run: shell: bash @@ -289,7 +294,7 @@ jobs: run: bun --cwd packages/app test:e2e:built --shard=${{ matrix.suite.shard }} ${{ env.PLAYWRIGHT_ARGS }} env: CI: true - timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} + timeout-minutes: ${{ (matrix.quarantine || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Verify service worker precaching and upgrades if: matrix.suite.shard == '2/2' && !matrix.quarantine @@ -302,14 +307,14 @@ jobs: run: bun --cwd packages/session-ui test:components ${{ env.PLAYWRIGHT_ARGS }} env: CI: true - timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} + timeout-minutes: ${{ (matrix.quarantine || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Run app component tests if: matrix.suite.id == 'app-components' run: bun --cwd packages/app test:components ${{ env.PLAYWRIGHT_ARGS }} env: CI: true - timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} + timeout-minutes: ${{ (matrix.quarantine || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Upload Playwright artifacts if: always() @@ -326,17 +331,18 @@ jobs: packages/app/component-tests/test-results packages/app/component-tests/playwright-report - # Branch rules require one e2e check per OS. These keep those names and pass only when every - # leg above passed, or when no leg was needed; a failing leg on either OS fails both checks. + # One check per OS for the browser suites: it passes only when every leg above passed, or when no leg was + # needed because the changed packages have no browser tests. e2e: name: e2e (${{ matrix.os }}) needs: [affected, e2e-suite] - if: ${{ !cancelled() && !inputs.quarantine && !github.event.pull_request.draft }} + if: ${{ !cancelled() && !inputs.quarantine && github.event_name != 'pull_request' && !github.event.pull_request.draft }} strategy: fail-fast: false matrix: os: [linux, windows] - runs-on: blacksmith-4vcpu-ubuntu-2404 + # Aggregates results only; these do not need a paid runner. + runs-on: ubuntu-24.04 steps: - name: Check browser test results env: @@ -344,9 +350,5 @@ jobs: SUITES: ${{ needs.e2e-suite.result }} EXPECTED: ${{ needs.affected.outputs.e2e == 'true' && 'success' || 'skipped' }} run: | - if [ "${{ matrix.os }}" = windows ] && [ "${{ github.base_ref }}" = beta ]; then - echo "Windows browser tests run on beta pushes and nightly, not on beta pull requests" - exit 0 - fi echo "affected packages: $AFFECTED, browser test legs: $SUITES (expected $EXPECTED)" test "$AFFECTED" = success && test "$SUITES" = "$EXPECTED" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3787ea643b..3321fda7c8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,6 +79,20 @@ jobs: INPUT_VERSION: ${{ inputs.version }} INPUT_RELEASE_NOTES: ${{ inputs.release_notes }} + # ---- Gate: the full check suite, before anything is published ------------------------------------------------ + + # release.yml does not wait for the check-test push run on the same commit, so a stable release would publish + # whatever that run later says. This gate runs the whole suite — unit tests on both OSes, the compiled builds + # and the browser suites — before any publish job below. Continuous dev and beta builds skip it: they publish + # their own channel from a dev push, whose check-test run covers the same commit. + verify: + needs: plan + if: needs.plan.outputs.channel == 'latest' + permissions: + contents: read + checks: write + uses: ./.github/workflows/check-test.yml + # A versioned release builds from a commit with its versions and changelog on the dispatched dev commit, # tagged v. Runs git only. tag: @@ -585,7 +599,7 @@ jobs: # Build provenance for the npm tarballs and Linux archives. No secrets; id-token only for Sigstore. publish-attestations: - needs: [plan, build-npm] + needs: [plan, build-npm, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.cli == 'true' && needs.plan.outputs.dry_run != 'true' }} runs-on: ubuntu-24.04 permissions: @@ -606,7 +620,7 @@ jobs: ${{ runner.temp }}/in/build-archives/opencode* publish-npm: - needs: [plan, sign-macos, sign-windows, publish-attestations] + needs: [plan, sign-macos, sign-windows, publish-attestations, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.cli == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -633,7 +647,7 @@ jobs: GH_TOKEN: ${{ github.token }} publish-cli: - needs: [plan, sign-macos, sign-windows, publish-npm] + needs: [plan, sign-macos, sign-windows, publish-npm, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.cli == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -664,7 +678,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ steps.cloudflare-token.outputs.token }} publish-desktop: - needs: [plan, sign-macos, sign-desktop-windows-installer] + needs: [plan, sign-macos, sign-desktop-windows-installer, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.desktop == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -699,7 +713,7 @@ jobs: env: *cloudflare publish-docker: - needs: [plan, build-docker] + needs: [plan, build-docker, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -719,7 +733,7 @@ jobs: GH_TOKEN: ${{ github.token }} publish-homebrew: - needs: [plan, sign-macos, sign-windows, publish-cli] + needs: [plan, sign-macos, sign-windows, publish-cli, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -748,7 +762,7 @@ jobs: # The AUR package installs the published npm tarballs, so it follows the npm publish. publish-aur: - needs: [plan, sign-macos, sign-windows, publish-npm] + needs: [plan, sign-macos, sign-windows, publish-npm, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -777,7 +791,7 @@ jobs: # WinGet manifests as data. Submission to microsoft/winget-pkgs stays paused until the packages are accepted. publish-winget: - needs: [plan, sign-windows, sign-desktop-windows-installer] + needs: [plan, sign-windows, sign-desktop-windows-installer, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 env: *env @@ -797,7 +811,7 @@ jobs: path: ${{ runner.temp }}/out publish-github-release: - needs: [plan, sign-macos, sign-windows, sign-desktop-windows-installer, publish-cli, publish-desktop] + needs: [plan, sign-macos, sign-windows, sign-desktop-windows-installer, publish-cli, publish-desktop, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -821,7 +835,7 @@ jobs: OPENCODE_RELEASE_NOTES: ${{ inputs.release_notes }} publish-discord: - needs: [plan, publish-github-release] + needs: [plan, publish-github-release, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.release == 'true' }} runs-on: ubuntu-24.04 environment: ${{ needs.plan.outputs.dry_run != 'true' && 'release' || '' }} @@ -851,7 +865,7 @@ jobs: # Brings the release commit back to dev. Pushing to dev is blocked by rulesets, so it opens a PR. publish-version-pr: - needs: [plan, tag, publish-npm, publish-cli, publish-desktop, publish-github-release] + needs: [plan, tag, publish-npm, publish-cli, publish-desktop, publish-github-release, verify] if: ${{ !cancelled() && !failure() && needs.plan.outputs.channel == 'latest' }} runs-on: ubuntu-24.04 permissions: