Skip to content

CI PR #9193 head 696276c0f6f9cd46d479217dae4ee0fdff231054 base f58a07f015bc9a1ffcba0250f3fe245080e0503c gate false #43385

CI PR #9193 head 696276c0f6f9cd46d479217dae4ee0fdff231054 base f58a07f015bc9a1ffcba0250f3fe245080e0503c gate false

CI PR #9193 head 696276c0f6f9cd46d479217dae4ee0fdff231054 base f58a07f015bc9a1ffcba0250f3fe245080e0503c gate false #43385

Workflow file for this run

# 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