Skip to content

Merge branch 'main' into codex/managed-image-short-secret-redaction #16507

Merge branch 'main' into codex/managed-image-short-secret-redaction

Merge branch 'main' into codex/managed-image-short-secret-redaction #16507

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Self-hosted runner PR workflow — triggered by copy-pr-bot.
#
# copy-pr-bot pushes PR code to pull-request/<number> branches only after
# a maintainer has vetted the changes (or the author is a trusted NVIDIA
# employee with signed commits). This ensures community PRs never run on
# self-hosted runners without explicit maintainer approval.
#
# Lightweight checks (lint, unit tests) still run on GitHub-hosted runners
# via the regular pr.yaml workflow, which triggers on pull_request events.
#
# See: https://docs.gha-runners.nvidia.com/platform/onboarding/pull-request-testing/
name: CI / Self-Hosted PR
on:
push:
branches:
- "pull-request/[0-9]+"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
get-pr-info:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
pr-info: ${{ steps.get-pr-info.outputs.pr-info }}
steps:
- id: get-pr-info
uses: nv-gha-runners/get-pr-info@090577647b8ddc4e06e809e264f7881650ecdccf # main
select-llama-cpp-generic-gpu:
needs: get-pr-info
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
selected: ${{ steps.changed.outputs.selected }}
steps:
- id: changed
name: Select llama.cpp generic GPU E2E from PR files
env:
GH_TOKEN: ${{ github.token }}
PR_INFO: ${{ needs.get-pr-info.outputs.pr-info }}
shell: bash
run: |
set -euo pipefail
pr_number="$(jq -er '.number | select(type == "number" and . > 0)' <<<"$PR_INFO")"
head_sha="$(jq -er '.head.sha | select(test("^[a-f0-9]{40}$"))' <<<"$PR_INFO")"
[[ "$head_sha" == "$GITHUB_SHA" ]] || {
echo "::error::Copied PR branch SHA does not match the current PR head" >&2
exit 1
}
if gh api --paginate --slurp \
"repos/$GITHUB_REPOSITORY/pulls/$pr_number/files?per_page=100" \
| jq -e '
flatten
| any(
.filename == ".github/workflows/e2e.yaml"
or .filename == ".github/workflows/pr-self-hosted.yaml"
or .filename == "scripts/install.sh"
or .filename == "test/e2e/live/llama-cpp-generic-gpu.test.ts"
or .filename == "test/e2e/live/gpu-e2e-helpers.ts"
or .filename == "test/e2e/mock-parity.json"
or .filename == "tools/e2e/cli-artifact-workflow-boundary.mts"
or .filename == "tools/e2e/workflow-boundary.mts"
or (.filename | startswith("managed-inference/presets/llama-cpp."))
or (.filename | startswith("managed-inference/recipes/llama-cpp."))
or (.filename | startswith("src/lib/inference/llama-cpp/"))
or (.filename | startswith("src/lib/readiness/"))
or .filename == "src/lib/onboard/fatal-runtime-preflight.ts"
or .filename == "src/lib/onboard/overlayfs-auto-fix.ts"
or .filename == "src/lib/onboard/preflight.ts"
or (.filename | startswith("src/lib/onboard/runtime-provider/docker-llama-cpp"))
)
' >/dev/null; then
selected=true
else
selected=false
fi
printf 'selected=%s\n' "$selected" >>"$GITHUB_OUTPUT"
llama-cpp-generic-gpu:
name: llama.cpp on generic NVIDIA GPU
needs:
- get-pr-info
- select-llama-cpp-generic-gpu
if: ${{ needs.select-llama-cpp-generic-gpu.outputs.selected == 'true' }}
runs-on: linux-amd64-gpu-rtxpro6000-latest-1
timeout-minutes: 120
env:
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/llama-cpp-generic-gpu
E2E_JOB: "1"
E2E_TARGET_ID: llama-cpp-generic-gpu
NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1"
NEMOCLAW_CLI_BIN: ${{ github.workspace }}/bin/nemoclaw.js
NEMOCLAW_E2E_EXPECTED_SHA: ${{ fromJSON(needs.get-pr-info.outputs.pr-info).head.sha }}
NEMOCLAW_E2E_SHARD: default
NEMOCLAW_LLAMA_CPP_QUALIFICATION_HEAD_SHA: ${{ fromJSON(needs.get-pr-info.outputs.pr-info).head.sha }}
NEMOCLAW_LLAMACPP_RECIPE: llama-cpp.nemotron-3-nano-30b-a3b.spark-single.v1
NEMOCLAW_NON_INTERACTIVE: "1"
NEMOCLAW_PROVIDER: install-llama-cpp
NEMOCLAW_RUN_LIVE_E2E: "1"
NEMOCLAW_SANDBOX_NAME: e2e-llamacpp-gpu
OPENSHELL_GATEWAY: nemoclaw
steps:
- name: Checkout exact PR head
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ fromJSON(needs.get-pr-info.outputs.pr-info).head.sha }}
- name: Prepare E2E workspace
uses: NVIDIA/NemoClaw/.github/actions/prepare-e2e@f6304bc25fc35bfaa441c8c2fbfee38f72805a75
- name: Bind E2E correlation identity
shell: bash
run: |
set -euo pipefail
correlation_id="$(node --input-type=module -e \
'import { randomUUID } from "node:crypto"; console.log(randomUUID())')"
[[ "$correlation_id" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$ ]]
printf 'NEMOCLAW_E2E_CORRELATION_ID=%s\n' "$correlation_id" >>"$GITHUB_ENV"
- name: Install OpenShell CLI
run: bash scripts/install-openshell.sh
- name: Run llama.cpp generic NVIDIA GPU live test
shell: bash
run: |
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH"
OPENSHELL_BIN="$(command -v openshell)"
export OPENSHELL_BIN
"$OPENSHELL_BIN" --version
npx tsx tools/e2e/live-vitest-invocation.mts run \
--test-path test/e2e/live/llama-cpp-generic-gpu.test.ts
- name: Upload llama.cpp generic NVIDIA GPU artifacts
if: always()
uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57
build-sandbox-images:
runs-on: linux-amd64-cpu4
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Resolve sandbox base image
uses: ./.github/actions/resolve-sandbox-base-image
- name: Build production image
env:
BASE_IMAGE: ${{ env.BASE_IMAGE }}
run: |
set -euo pipefail
build_args=(--build-arg "BASE_IMAGE=${BASE_IMAGE}")
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production .
- name: Build sandbox test image (fixtures layered on production)
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production -t nemoclaw-sandbox-test .
- name: Save images to tarballs
run: |
docker save nemoclaw-sandbox-test | gzip > /tmp/sandbox-test-image.tar.gz
docker save nemoclaw-production | gzip > /tmp/isolation-image.tar.gz
- name: Upload sandbox test image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sandbox-test-image
path: /tmp/sandbox-test-image.tar.gz
retention-days: 1
if-no-files-found: error
- name: Upload isolation image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: isolation-image
path: /tmp/isolation-image.tar.gz
retention-days: 1
if-no-files-found: error
build-sandbox-images-arm64:
runs-on: linux-arm64-cpu4
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Resolve sandbox base image
uses: ./.github/actions/resolve-sandbox-base-image
- name: Build production image on arm64
env:
BASE_IMAGE: ${{ env.BASE_IMAGE }}
run: |
set -euo pipefail
build_args=(--build-arg "BASE_IMAGE=${BASE_IMAGE}")
scripts/check-production-build-args.sh "${build_args[@]}"
docker build "${build_args[@]}" -t nemoclaw-production-arm64 .
- name: Build sandbox test image on arm64
run: docker build -f test/Dockerfile.sandbox --build-arg BASE_IMAGE=nemoclaw-production-arm64 -t nemoclaw-sandbox-test-arm64 .
test-e2e-sandbox:
runs-on: linux-amd64-cpu4
timeout-minutes: 15
needs: build-sandbox-images
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sandbox-test-image
path: /tmp
- name: Load image
run: gunzip -c /tmp/sandbox-test-image.tar.gz | docker load
- name: Run sandbox E2E tests
run: docker run --rm -v "${{ github.workspace }}/test:/opt/test" nemoclaw-sandbox-test /opt/test/e2e-test.sh
test-e2e-gateway-isolation:
runs-on: linux-amd64-cpu4
timeout-minutes: 15
needs: build-sandbox-images
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22.19.0
cache: npm
- name: Install root dependencies
run: npm ci --ignore-scripts
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: isolation-image
path: /tmp
- name: Load image
run: |
gunzip -c /tmp/isolation-image.tar.gz | docker load
docker image inspect nemoclaw-production >/dev/null
- name: Run glibc probe lifecycle regression
env:
NEMOCLAW_RUN_GLIBC_PROBE_DOCKER_E2E: "1"
NEMOCLAW_TEST_IMAGE: nemoclaw-production
run: npx vitest run --project integration test/image-compatibility-docker-lifecycle.test.ts --silent=false --reporter=default
- name: Run gateway isolation E2E tests
run: NEMOCLAW_TEST_IMAGE=nemoclaw-production bash test/e2e-gateway-isolation.sh
test-e2e-port-overrides:
runs-on: linux-amd64-cpu4
timeout-minutes: 10
needs: build-sandbox-images
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: isolation-image
path: /tmp
- name: Load image
run: gunzip -c /tmp/isolation-image.tar.gz | docker load
- name: Run port override E2E tests
run: NEMOCLAW_TEST_IMAGE=nemoclaw-production bash test/e2e-port-overrides.sh
test-non-root-sandbox-smoke:
runs-on: linux-amd64-cpu4
timeout-minutes: 5
needs: build-sandbox-images
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: isolation-image
path: /tmp
- name: Load image
run: gunzip -c /tmp/isolation-image.tar.gz | docker load
- name: Run non-root sandbox smoke test
run: NEMOCLAW_TEST_IMAGE=nemoclaw-production bash test/e2e-non-root-smoke.sh