🤖 ci: pin all GitHub Actions to SHA hashes #3611
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: ["**"] | |
| merge_group: | |
| workflow_dispatch: | |
| inputs: | |
| test_filter: | |
| description: 'Optional test filter (e.g., "workspace", "tests/file.test.ts", or "-t pattern")' | |
| required: false | |
| type: string | |
| concurrency: | |
| # For PRs: group by PR number so pushes to same PR cancel previous runs | |
| # For main: use unique SHA so builds never cancel each other (we want all artifacts) | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| # Permissions for GCP workload identity authentication (Windows code signing on main) | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| # Detect what files changed to determine which jobs/tests to run | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| src: ${{ steps.filter.outputs.src }} | |
| config: ${{ steps.filter.outputs.config }} | |
| backend: ${{ steps.filter.outputs.backend }} | |
| browser: ${{ steps.filter.outputs.browser }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| id: filter | |
| with: | |
| filters: | | |
| src: | |
| - 'src/**' | |
| - 'tests/**' | |
| - 'vscode/**' | |
| backend: | |
| - 'src/node/**' | |
| - 'src/cli/**' | |
| - 'src/desktop/**' | |
| - 'src/common/**' | |
| - 'tests/ipc/**' | |
| - 'tests/runtime/**' | |
| browser: | |
| - 'src/browser/**' | |
| - 'tests/ui/**' | |
| config: | |
| - '.github/**' | |
| - 'jest.config.cjs' | |
| - 'babel.config.cjs' | |
| - 'package.json' | |
| - 'bun.lockb' | |
| - 'tsconfig*.json' | |
| - 'vite*.ts' | |
| - 'Makefile' | |
| - 'electron-builder.yml' | |
| static-check: | |
| name: Static Checks | |
| if: github.event_name != 'push' || github.actor != 'github-merge-queue[bot]' | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - run: ./scripts/generate-version.sh | |
| - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | |
| with: | |
| path: ~/.local/bin/shfmt | |
| key: ${{ runner.os }}-shfmt-latest | |
| - name: Install shfmt | |
| run: | | |
| set -euo pipefail | |
| if [[ ! -f "$HOME/.local/bin/shfmt" ]] || ! "$HOME/.local/bin/shfmt" --version >/dev/null 2>&1; then | |
| # webinstall.dev can be flaky; retry and force HTTP/1.1 to avoid HTTP/2 stream errors. | |
| curl --retry 5 --retry-all-errors --retry-delay 2 -LsSf --http1.1 https://webinstall.dev/shfmt | bash | |
| fi | |
| if ! "$HOME/.local/bin/shfmt" --version >/dev/null 2>&1; then | |
| echo "Error: shfmt install failed" | |
| exit 1 | |
| fi | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install shellcheck | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y shellcheck | |
| - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 | |
| with: | |
| extra_nix_config: | | |
| experimental-features = nix-command flakes | |
| - run: | | |
| set -euo pipefail | |
| curl --retry 5 --retry-all-errors --retry-delay 2 -LsSf https://astral.sh/uv/install.sh | sh | |
| - run: echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - run: make -j static-check | |
| env: | |
| # Used by zizmor to lint third-party GitHub actions | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| test-unit: | |
| name: Test / Unit | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - run: make build-main | |
| # workflow_dispatch inputs are only triggerable by repo members, so direct | |
| # interpolation is acceptable and preserves shell quoting in the filter. | |
| - run: bun test --coverage --coverage-reporter=lcov ${{ github.event.inputs.test_filter || 'src' }} # zizmor: ignore[template-injection] | |
| - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: unit-tests | |
| fail_ci_if_error: false | |
| test-integration: | |
| name: Test / Integration | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| timeout-minutes: 10 | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - run: make build-main | |
| # workflow_dispatch inputs are only triggerable by repo members, so direct | |
| # interpolation is acceptable and preserves shell quoting in the filter. | |
| - name: Run integration tests | |
| run: | # zizmor: ignore[template-injection] | |
| set -euo pipefail | |
| # Manual override (workflow_dispatch input) | |
| if [[ -n "${{ github.event.inputs.test_filter }}" ]]; then | |
| TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent ${{ github.event.inputs.test_filter }} | |
| exit 0 | |
| fi | |
| # Skip if no relevant changes | |
| if [[ "$BACKEND" != "true" && "$BROWSER" != "true" ]]; then | |
| echo "No backend or browser changes - skipping integration tests" | |
| exit 0 | |
| fi | |
| # Backend changed: run ALL integration tests (includes tests/ui) | |
| if [[ "$BACKEND" == "true" ]]; then | |
| echo "Backend changes detected - running all integration tests" | |
| TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent tests/ | |
| else | |
| # Browser-only changes: run tests/ui | |
| echo "Browser changes detected - running tests/ui" | |
| TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent tests/ui/ | |
| fi | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| BACKEND: ${{ needs.changes.outputs.backend }} | |
| BROWSER: ${{ needs.changes.outputs.browser }} | |
| - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: integration-tests | |
| fail_ci_if_error: false | |
| test-storybook: | |
| name: Test / Storybook | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && github.event.inputs.test_filter == '' && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - uses: ./.github/actions/setup-playwright | |
| - run: make storybook-build | |
| - run: | | |
| bun x http-server storybook-static -p 6006 & | |
| for i in {1..30}; do | |
| if curl -sf http://127.0.0.1:6006 >/dev/null; then | |
| echo "Storybook ready" | |
| break | |
| fi | |
| echo "Waiting for Storybook... ($i/30)" | |
| sleep 0.5 | |
| done | |
| # Fail the step if Storybook never became reachable. | |
| curl -sf http://127.0.0.1:6006 >/dev/null | |
| - run: make test-storybook | |
| test-e2e: | |
| name: Test / E2E (${{ matrix.os }}) | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && github.event.inputs.test_filter == '' && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: linux | |
| runner: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| - os: macos | |
| runner: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest' }} | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - name: Install xvfb | |
| if: matrix.os == 'linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb | |
| - uses: ./.github/actions/setup-playwright | |
| - name: Run tests | |
| if: matrix.os == 'linux' | |
| run: xvfb-run -a make test-e2e | |
| env: | |
| ELECTRON_DISABLE_SANDBOX: 1 | |
| - name: Run tests | |
| if: matrix.os == 'macos' | |
| run: make test-e2e PLAYWRIGHT_ARGS="tests/e2e/scenarios/windowLifecycle.spec.ts" | |
| smoke-server: | |
| name: Smoke / Server | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - run: make build | |
| - run: npm pack | |
| - env: | |
| SERVER_PORT: 3001 | |
| SERVER_HOST: localhost | |
| STARTUP_TIMEOUT: 30 | |
| run: | | |
| # shellcheck disable=SC2012 # ls is fine here - known filename pattern in controlled directory | |
| TARBALL=$(ls mux-*.tgz | head -1) | |
| PACKAGE_TARBALL="$TARBALL" ./scripts/smoke-test.sh | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| if: failure() | |
| with: | |
| name: smoke-server-logs | |
| path: /tmp/tmp.*/server.log | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| smoke-docker: | |
| name: Smoke / Docker | |
| # Only run on merge queue (not every PR, not on merge-queue push to main) | |
| if: github.event_name == 'merge_group' || (github.event_name == 'push' && github.actor != 'github-merge-queue[bot]') | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 | |
| - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| context: . | |
| load: true | |
| tags: mux-server:test | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Test container | |
| run: | | |
| docker run -d --name mux-test -p 3000:3000 mux-server:test | |
| for i in {1..30}; do | |
| curl -sf http://localhost:3000/health && break | |
| echo "Waiting... ($i/30)" | |
| sleep 1 | |
| done | |
| curl -sf http://localhost:3000/health | grep -q '"status":"ok"' | |
| curl -sf http://localhost:3000/version | grep -q '"mode":"server"' | |
| - if: failure() | |
| run: docker logs mux-test | |
| - if: always() | |
| run: docker rm -f mux-test || true | |
| build-linux: | |
| name: Build / Linux | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - run: bun run build | |
| - run: make dist-linux | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-linux | |
| path: release/*.AppImage | |
| retention-days: 30 | |
| if-no-files-found: error | |
| build-macos: | |
| name: Build / macOS | |
| needs: [changes] | |
| if: ${{ needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true' }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-15' || 'macos-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - name: Setup code signing | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| run: ./scripts/setup-macos-signing.sh | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} | |
| AC_APIKEY_P8_BASE64: ${{ secrets.AC_APIKEY_P8_BASE64 }} | |
| AC_APIKEY_ID: ${{ secrets.AC_APIKEY_ID }} | |
| AC_APIKEY_ISSUER_ID: ${{ secrets.AC_APIKEY_ISSUER_ID }} | |
| - run: make dist-mac | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-macos-x64 | |
| path: release/*-x64.dmg | |
| retention-days: 30 | |
| if-no-files-found: error | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-macos-arm64 | |
| path: release/*-arm64.dmg | |
| retention-days: 30 | |
| if-no-files-found: error | |
| build-windows: | |
| name: Build / Windows | |
| needs: [changes] | |
| # Windows builds are slow - only run in merge queue and main pushes, not on every PR push | |
| if: ${{ (github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main')) && (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') }} | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - name: Add MSYS2 make to PATH | |
| shell: pwsh | |
| run: | | |
| # MSYS2 is pre-installed on Windows runners, just needs to be in PATH | |
| # Install make via pacman (faster than choco) | |
| C:\msys64\usr\bin\pacman.exe -S --noconfirm make | |
| echo "C:\msys64\usr\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Verify tools | |
| shell: bash | |
| run: | | |
| make --version | |
| bun --version | |
| magick --version | head -1 | |
| - run: bun run build | |
| - name: Setup code signing | |
| id: signing | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| uses: ./.github/actions/setup-windows-signing | |
| with: | |
| ev_signing_cert: ${{ secrets.EV_SIGNING_CERT }} | |
| gcp_workload_id_provider: ${{ vars.GCP_WORKLOAD_ID_PROVIDER }} | |
| gcp_service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} | |
| - name: Package for Windows | |
| run: make dist-win | |
| env: | |
| # EV signing environment variables (used by custom sign script if configured) | |
| EV_KEYSTORE: ${{ vars.EV_KEYSTORE }} | |
| EV_KEY: ${{ vars.EV_KEY }} | |
| EV_TSA_URL: ${{ vars.EV_TSA_URL }} | |
| GCLOUD_ACCESS_TOKEN: ${{ steps.signing.outputs.gcloud_access_token }} | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-windows | |
| path: release/*.exe | |
| retention-days: 30 | |
| if-no-files-found: error | |
| build-vscode: | |
| name: Build / VS Code | |
| needs: [changes] | |
| if: ${{ (needs.changes.outputs.src == 'true' || needs.changes.outputs.config == 'true') && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: ./.github/actions/setup-mux | |
| - uses: ./.github/actions/build-vscode-extension | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: build-vscode | |
| path: vscode/mux-*.vsix | |
| retention-days: 30 | |
| if-no-files-found: error | |
| codex-comments: | |
| name: Codex Comments | |
| if: github.event_name == 'pull_request' | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: ./scripts/check_codex_comments.sh ${{ github.event.pull_request.number }} | |
| # Single required check - configure branch protection to require only this job | |
| required: | |
| name: Required | |
| if: ${{ always() && (github.event_name != 'push' || github.actor != 'github-merge-queue[bot]') }} | |
| needs: | |
| - changes | |
| - static-check | |
| - test-unit | |
| - test-integration | |
| - test-storybook | |
| - test-e2e | |
| - smoke-server | |
| - smoke-docker | |
| - build-linux | |
| - build-macos | |
| - build-windows | |
| - build-vscode | |
| - codex-comments | |
| runs-on: ubuntu-latest | |
| steps: | |
| - run: | | |
| results="${{ join(needs.*.result, ' ') }}" | |
| echo "Job results: $results" | |
| for result in $results; do | |
| if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then | |
| echo "❌ CI failed" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ All checks passed" |