CI PR #8964 head debc957d9a5d96db73fb4bbed69ce3b741c6c83b base 561b52807a05f4cab5ca937e330c4104504f7268 gate true #42159
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
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| name: CI / Pull Request | |
| run-name: "CI PR #${{ github.event.pull_request.number }} head ${{ github.event.pull_request.head.sha }} base ${{ github.event.pull_request.base.sha }} gate ${{ github.event.action != 'edited' || github.event.changes.base != null }}" | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, edited] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.action != 'edited' || github.event.changes.base != null }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Detect which files changed so we can skip expensive jobs for docs-only PRs. | |
| changes: | |
| if: ${{ github.event.action != 'edited' || github.event.changes.base != null }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Detect changed paths | |
| id: filter | |
| uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| with: | |
| predicate-quantifier: every | |
| filters: | | |
| code: | |
| - '**' | |
| - '!**/*.md' | |
| - '!docs/**' | |
| docs-only-checks: | |
| needs: changes | |
| if: needs.changes.outputs.code != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup Node.js for docs-only checks | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| - name: Install docs-only check dependencies | |
| run: npm install --ignore-scripts | |
| - name: Install hadolint | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| HADOLINT_VERSION="v2.14.0" | |
| HADOLINT_URL="https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-linux-x86_64" | |
| HADOLINT_SHA256="6bf226944684f56c84dd014e8b979d27425c0148f61b3bd99bcc6f39e9dc5a47" | |
| curl -fsSL -o /usr/local/bin/hadolint "$HADOLINT_URL" | |
| ACTUAL=$(sha256sum /usr/local/bin/hadolint | awk '{print $1}') | |
| [ "$HADOLINT_SHA256" = "$ACTUAL" ] || { echo "::error::hadolint checksum mismatch"; exit 1; } | |
| chmod +x /usr/local/bin/hadolint | |
| - name: Resolve checked-out merge base for docs-only checks | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DOCS_ONLY_FROM_REF="$(git merge-base HEAD "origin/${GITHUB_BASE_REF}")" | |
| [ -n "$DOCS_ONLY_FROM_REF" ] | |
| echo "DOCS_ONLY_FROM_REF=$DOCS_ONLY_FROM_REF" >> "$GITHUB_ENV" | |
| - name: Run docs-only hook checks | |
| run: npx prek run --from-ref "$DOCS_ONLY_FROM_REF" --to-ref HEAD | |
| - name: Verify platform matrix is in sync | |
| run: python3 scripts/generate-platform-docs.py --check | |
| static-checks: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-static-checks | |
| .github/actions/ci-build-typecheck | |
| .github/actions/ci-cli-coverage-shard | |
| .github/actions/ci-cli-coverage-merge | |
| .github/actions/ci-plugin-coverage | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Run static checks | |
| uses: ./.trusted-ci-actions/.github/actions/ci-static-checks | |
| build-typecheck: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-static-checks | |
| .github/actions/ci-build-typecheck | |
| .github/actions/ci-cli-coverage-shard | |
| .github/actions/ci-cli-coverage-merge | |
| .github/actions/ci-plugin-coverage | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Run build and type checks | |
| uses: ./.trusted-ci-actions/.github/actions/ci-build-typecheck | |
| installer-integration: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Run installer integration tests | |
| uses: ./.trusted-ci-actions/.github/actions/ci-installer-integration | |
| wechat-runtime-audit: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted WeChat runtime audit | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-wechat-audit | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-wechat-runtime-audit | |
| .github/actions/ci-reviewed-npm-audit/verify-and-install-npm.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Audit locked WeChat runtime graph | |
| uses: ./.trusted-wechat-audit/.github/actions/ci-wechat-runtime-audit | |
| with: | |
| target-root: ${{ github.workspace }} | |
| report-dir: artifacts/wechat-runtime-audit | |
| - name: Upload WeChat runtime audit evidence | |
| if: ${{ always() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: wechat-runtime-audit-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: artifacts/wechat-runtime-audit | |
| if-no-files-found: error | |
| retention-days: 14 | |
| reviewed-npm-audit: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted reviewed npm audit | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-reviewed-npm-audit | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-reviewed-npm-audit | |
| ci/npm-audit-exceptions.json | |
| ci/reviewed-npm-audit.json | |
| scripts/audit-reviewed-npm-graph.mts | |
| scripts/lib/openclaw-npm-remediation.mts | |
| scripts/lib/reviewed-npm-archive.mts | |
| scripts/lib/reviewed-npm-audit.mts | |
| sparse-checkout-cone-mode: false | |
| - name: Audit reviewed production npm graphs | |
| uses: ./.trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit | |
| with: | |
| target-root: ${{ github.workspace }} | |
| report-dir: artifacts/reviewed-npm-audit | |
| cli-test-shards: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-static-checks | |
| .github/actions/ci-build-typecheck | |
| .github/actions/ci-cli-coverage-shard | |
| .github/actions/ci-cli-coverage-merge | |
| .github/actions/ci-plugin-coverage | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Run CLI coverage shard | |
| uses: ./.trusted-ci-actions/.github/actions/ci-cli-coverage-shard | |
| with: | |
| shard: ${{ matrix.shard }} | |
| shard-count: "12" | |
| cli-tests: | |
| needs: | |
| - changes | |
| - cli-test-shards | |
| if: ${{ always() && needs.changes.outputs.code == 'true' }} | |
| permissions: | |
| actions: read | |
| code-quality: write | |
| contents: read | |
| pull-requests: read | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Verify CLI shards completed | |
| env: | |
| CLI_SHARD_RESULT: ${{ needs['cli-test-shards'].result }} | |
| GH_TOKEN: ${{ github.token }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| RUN_ID: ${{ github.run_id }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -u | |
| if [ "$CLI_SHARD_RESULT" != "success" ]; then | |
| details="$RUN_URL" | |
| if jobs_json="$(gh api \ | |
| "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/attempts/$RUN_ATTEMPT/jobs?per_page=100" \ | |
| 2>/dev/null)"; then | |
| if job_ids="$(jq -er ' | |
| if ((.total_count | type) != "number") or | |
| (.total_count < 0) or | |
| (.total_count > 100) or | |
| ((.total_count | floor) != .total_count) or | |
| ((.jobs | type) != "array") or | |
| ((.jobs | length) != .total_count) | |
| then error("invalid workflow job listing") | |
| else | |
| [.jobs[] | | |
| select((.name | type) == "string") | | |
| select(.name | test("^cli-test-shards \\(([1-9]|1[0-2])\\)$")) | | |
| select(.conclusion != "success")] as $failed | | |
| if ($failed | length) == 0 or | |
| ($failed | length) > 12 or | |
| (($failed | map(.name) | unique | length) != ($failed | length)) | |
| then error("invalid failed CLI shard listing") | |
| else | |
| $failed[] | | |
| if ((.id | type) != "number") or | |
| (.id < 1) or | |
| (.id > 9007199254740991) or | |
| ((.id | floor) != .id) or | |
| (.status != "completed") or | |
| ((.conclusion | type) != "string") | |
| then error("invalid failed CLI shard") | |
| else .id | |
| end | |
| end | |
| end | |
| ' <<<"$jobs_json" 2>/dev/null)"; then | |
| details="" | |
| while IFS= read -r job_id; do | |
| [ -n "$details" ] && details="${details}; " | |
| details="${details}${RUN_URL}/job/${job_id}" | |
| done <<<"$job_ids" | |
| fi | |
| fi | |
| echo "::error title=CLI coverage shards failed::Expected success, got ${CLI_SHARD_RESULT}. Details: ${details}" | |
| exit 1 | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-static-checks | |
| .github/actions/ci-build-typecheck | |
| .github/actions/ci-cli-coverage-shard | |
| .github/actions/ci-cli-coverage-merge | |
| .github/actions/ci-plugin-coverage | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Merge CLI coverage | |
| uses: ./.trusted-ci-actions/.github/actions/ci-cli-coverage-merge | |
| with: | |
| shard-count: "12" | |
| plugin-tests: | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| permissions: | |
| code-quality: write | |
| contents: read | |
| pull-requests: read | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Checkout trusted CI actions | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| path: .trusted-ci-actions | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github/actions/ci-static-checks | |
| .github/actions/ci-build-typecheck | |
| .github/actions/ci-cli-coverage-shard | |
| .github/actions/ci-cli-coverage-merge | |
| .github/actions/ci-plugin-coverage | |
| .github/actions/ci-installer-integration | |
| .github/actions/ci-install-dependencies.sh | |
| sparse-checkout-cone-mode: false | |
| - name: Run plugin coverage | |
| uses: ./.trusted-ci-actions/.github/actions/ci-plugin-coverage | |
| checks: | |
| needs: | |
| - changes | |
| - docs-only-checks | |
| - static-checks | |
| - build-typecheck | |
| - installer-integration | |
| - wechat-runtime-audit | |
| - reviewed-npm-audit | |
| - cli-tests | |
| - plugin-tests | |
| if: always() | |
| permissions: | |
| actions: read | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - name: Verify required PR checks | |
| env: | |
| CI_REQUIRED: ${{ github.event.action != 'edited' || github.event.changes.base != null }} | |
| CODE_CHANGED: ${{ needs.changes.outputs.code }} | |
| CHANGES_RESULT: ${{ needs.changes.result }} | |
| DOCS_ONLY_RESULT: ${{ needs['docs-only-checks'].result }} | |
| STATIC_RESULT: ${{ needs['static-checks'].result }} | |
| BUILD_TYPECHECK_RESULT: ${{ needs['build-typecheck'].result }} | |
| INSTALLER_INTEGRATION_RESULT: ${{ needs['installer-integration'].result }} | |
| WECHAT_RUNTIME_AUDIT_RESULT: ${{ needs['wechat-runtime-audit'].result }} | |
| REVIEWED_NPM_AUDIT_RESULT: ${{ needs['reviewed-npm-audit'].result }} | |
| CLI_TESTS_RESULT: ${{ needs['cli-tests'].result }} | |
| GH_TOKEN: ${{ github.token }} | |
| PLUGIN_TESTS_RESULT: ${{ needs['plugin-tests'].result }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| RUN_ID: ${{ github.run_id }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$CI_REQUIRED" != "true" ]; then | |
| echo "Metadata-only PR edit; the existing head and base CI results remain valid." | |
| exit 0 | |
| fi | |
| job_listing_state="uninitialized" | |
| jobs_json="" | |
| dependency_url="$RUN_URL" | |
| failed=0 | |
| load_job_listing() { | |
| if [ "$job_listing_state" != "uninitialized" ]; then | |
| return | |
| fi | |
| if jobs_json="$(gh api \ | |
| "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/attempts/$RUN_ATTEMPT/jobs?per_page=100" \ | |
| 2>/dev/null)" && jq -e ' | |
| ((.total_count | type) == "number") and | |
| (.total_count >= 0) and | |
| (.total_count <= 100) and | |
| ((.total_count | floor) == .total_count) and | |
| ((.jobs | type) == "array") and | |
| ((.jobs | length) == .total_count) | |
| ' <<<"$jobs_json" >/dev/null 2>&1; then | |
| job_listing_state="ready" | |
| else | |
| job_listing_state="failed" | |
| fi | |
| } | |
| resolve_dependency_url() { | |
| local name="$1" | |
| local result="$2" | |
| local job_id="" | |
| dependency_url="$RUN_URL" | |
| case "$name" in | |
| changes|docs-only-checks|static-checks|build-typecheck|installer-integration|wechat-runtime-audit|reviewed-npm-audit|cli-tests|plugin-tests) ;; | |
| *) return ;; | |
| esac | |
| load_job_listing | |
| if [ "$job_listing_state" = "ready" ] && job_id="$(jq -er \ | |
| --arg name "$name" \ | |
| --arg result "$result" ' | |
| [.jobs[] | select(.name == $name)] as $matching | | |
| if ($matching | length) != 1 | |
| then error("missing or duplicate dependency job") | |
| elif (($matching[0].id | type) != "number") or | |
| ($matching[0].id < 1) or | |
| ($matching[0].id > 9007199254740991) or | |
| (($matching[0].id | floor) != $matching[0].id) or | |
| ($matching[0].status != "completed") or | |
| (($matching[0].conclusion | type) != "string") or | |
| ($matching[0].conclusion != $result) | |
| then error("invalid dependency job") | |
| else $matching[0].id | |
| end | |
| ' <<<"$jobs_json" 2>/dev/null)"; then | |
| dependency_url="${RUN_URL}/job/${job_id}" | |
| fi | |
| } | |
| require_success() { | |
| local name="$1" | |
| local result="$2" | |
| if [ "$result" != "success" ]; then | |
| resolve_dependency_url "$name" "$result" | |
| echo "::error title=${name} failed::Expected success, got ${result}. Details: ${dependency_url}" | |
| failed=1 | |
| fi | |
| } | |
| allow_success_or_skipped() { | |
| local name="$1" | |
| local result="$2" | |
| case "$result" in | |
| success|skipped) ;; | |
| *) | |
| resolve_dependency_url "$name" "$result" | |
| echo "::error title=${name} failed::Expected success or skipped, got ${result}. Details: ${dependency_url}" | |
| failed=1 | |
| ;; | |
| esac | |
| } | |
| require_success "changes" "$CHANGES_RESULT" | |
| if [ "$CODE_CHANGED" = "true" ]; then | |
| allow_success_or_skipped "docs-only-checks" "$DOCS_ONLY_RESULT" | |
| require_success "static-checks" "$STATIC_RESULT" | |
| require_success "build-typecheck" "$BUILD_TYPECHECK_RESULT" | |
| require_success "installer-integration" "$INSTALLER_INTEGRATION_RESULT" | |
| require_success "wechat-runtime-audit" "$WECHAT_RUNTIME_AUDIT_RESULT" | |
| require_success "reviewed-npm-audit" "$REVIEWED_NPM_AUDIT_RESULT" | |
| require_success "cli-tests" "$CLI_TESTS_RESULT" | |
| require_success "plugin-tests" "$PLUGIN_TESTS_RESULT" | |
| else | |
| require_success "docs-only-checks" "$DOCS_ONLY_RESULT" | |
| allow_success_or_skipped "static-checks" "$STATIC_RESULT" | |
| allow_success_or_skipped "build-typecheck" "$BUILD_TYPECHECK_RESULT" | |
| allow_success_or_skipped "installer-integration" "$INSTALLER_INTEGRATION_RESULT" | |
| allow_success_or_skipped "wechat-runtime-audit" "$WECHAT_RUNTIME_AUDIT_RESULT" | |
| allow_success_or_skipped "reviewed-npm-audit" "$REVIEWED_NPM_AUDIT_RESULT" | |
| allow_success_or_skipped "cli-tests" "$CLI_TESTS_RESULT" | |
| allow_success_or_skipped "plugin-tests" "$PLUGIN_TESTS_RESULT" | |
| fi | |
| [ "$failed" -eq 0 ] | |
| # Sandbox image builds and E2E tests have moved to pr-self-hosted.yaml, | |
| # which runs on NVIDIA self-hosted runners via copy-pr-bot. | |
| # See: .github/workflows/pr-self-hosted.yaml |