fix(inference): preflight NEMOCLAW_VLLM_MODEL on sandbox connect #3492
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: E2E / Advisor | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| workflow_dispatch: | |
| inputs: | |
| base_ref: | |
| description: Base ref to diff against | |
| required: false | |
| default: origin/main | |
| head_ref: | |
| description: Head ref to diff | |
| required: false | |
| default: HEAD | |
| target_repo: | |
| description: Optional repo to analyze, e.g. NVIDIA/NemoClaw | |
| required: false | |
| type: string | |
| default: "" | |
| target_pr: | |
| description: Optional pull request number in target_repo to analyze | |
| required: false | |
| type: string | |
| default: "" | |
| target_base: | |
| description: Base branch to use with target_repo/target_pr manual analysis | |
| required: false | |
| type: string | |
| default: main | |
| run_analysis: | |
| description: Run E2E recommendation analysis | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: read | |
| # `actions: write` lets the trusted advisor dispatcher trigger the selected | |
| # E2E workflow for eligible NVIDIA-owned PRs after validating the requested | |
| # jobs against the target workflow's own selective-dispatch predicates. | |
| actions: write | |
| # `pull-requests: write` is required for the advisor to post (or update) a | |
| # comment on a PR via POST /repos/:o/:r/issues/:n/comments. Even though the | |
| # endpoint lives under `/issues/`, for GITHUB_TOKEN the permission that | |
| # actually gates PR comments is `pull-requests`, not `issues`. With only | |
| # `pull-requests: read`, the endpoint returns 403 "Resource not accessible | |
| # by integration" despite `issues: write`. See the comment step below and | |
| # https://github.com/orgs/community/discussions/56632. | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: e2e-advisor-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| advise: | |
| name: E2E recommendation | |
| if: ${{ github.repository == 'NVIDIA/NemoClaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'NVIDIA/NemoClaw') }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| # Pin the Pi SDK to a known-good version. Updates should go through | |
| # the normal dependency-review path (e.g. Dependabot) so a compromised | |
| # upstream release cannot execute automatically in this secret-bearing job. | |
| PI_SDK_VERSION: "0.74.0" | |
| # Keep the advisor timeout shorter than the GitHub job timeout so the | |
| # analyzer can write failure artifacts, summaries, and PR comments. | |
| E2E_ADVISOR_TIMEOUT_MS: "900000" | |
| E2E_ADVISOR_HEARTBEAT_MS: "60000" | |
| # Cap automatic fan-out from noisy/flaky advisor recommendations until | |
| # the dispatcher has enough production history to justify a higher limit. | |
| E2E_ADVISOR_AUTO_DISPATCH_MAX_JOBS: "6" | |
| # The trusted checkout always lives at this path and is the only source | |
| # of advisor implementation code executed in this job. The PR content, | |
| # if any, is mounted as read-only analysis data under PR_WORKDIR. | |
| ADVISOR_DIR: ${{ github.workspace }}/advisor | |
| steps: | |
| # Trusted-code boundary: the advisor implementation (analyze.mts, | |
| # dispatch.mts, comment.mts, schema.json) is always fetched | |
| # from the main branch of this repo, regardless of what the PR changed. | |
| # This prevents a future PR from modifying advisor code and executing | |
| # arbitrary Node in a job that holds API keys and a write-scoped token. | |
| - name: Checkout trusted advisor code (main) | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| repository: NVIDIA/NemoClaw | |
| ref: main | |
| path: advisor | |
| persist-credentials: false | |
| # PR content is checked out as inert analysis data only. Nothing from | |
| # this directory is invoked as code by the advisor scripts; it is read | |
| # via `git diff` / `fs.readFileSync` from inside the trusted scripts. | |
| - name: Checkout PR workspace (read-only data) | |
| if: ${{ github.event_name == 'pull_request' }} | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| path: pr-workdir | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # For manual workflow_dispatch against this repo (no target_repo given), | |
| # check out the ref the maintainer dispatched against as the workdir. | |
| # workflow_dispatch requires write access, so this ref is trusted for | |
| # the purposes of analysis-data access, though we still never execute | |
| # it as code. | |
| - name: Checkout dispatch workspace (read-only data) | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.target_repo == '' }} | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| path: pr-workdir | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| - name: Set default advisor workdir | |
| run: echo "ADVISOR_WORKDIR=$GITHUB_WORKSPACE/pr-workdir" >> "$GITHUB_ENV" | |
| - name: Prepare target PR checkout | |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.target_repo != '' && inputs.target_pr != '' }} | |
| env: | |
| TARGET_REPO: ${{ inputs.target_repo }} | |
| TARGET_PR: ${{ inputs.target_pr }} | |
| TARGET_BASE: ${{ inputs.target_base }} | |
| run: | | |
| TARGET_DIR=/tmp/e2e-advisor-target | |
| rm -rf "$TARGET_DIR" | |
| mkdir -p "$TARGET_DIR" | |
| git -C "$TARGET_DIR" init | |
| git -C "$TARGET_DIR" remote add target "https://github.com/${TARGET_REPO}.git" | |
| git -C "$TARGET_DIR" fetch --no-tags target "$TARGET_BASE" | |
| git -C "$TARGET_DIR" fetch --no-tags target "pull/${TARGET_PR}/head:refs/remotes/target/pr-${TARGET_PR}" | |
| git -C "$TARGET_DIR" checkout --detach "refs/remotes/target/pr-${TARGET_PR}" | |
| echo "ADVISOR_WORKDIR=$TARGET_DIR" >> "$GITHUB_ENV" | |
| # Pinned SDK install. The version is held in PI_SDK_VERSION above so | |
| # the pin is reviewed as a code change, not silently inherited from | |
| # whatever @latest points to at runtime. Install into an isolated temp | |
| # prefix, then expose that dependency tree to the trusted advisor script. | |
| - name: Install Pi SDK | |
| run: | | |
| PI_SDK_DIR="$RUNNER_TEMP/pi-sdk" | |
| npm install --prefix "$PI_SDK_DIR" --ignore-scripts --no-save --package-lock=false --before=2026-05-14T00:00:00.000Z "@earendil-works/pi-coding-agent@${PI_SDK_VERSION}" | |
| rm -rf "$ADVISOR_DIR/node_modules" | |
| ln -s "$PI_SDK_DIR/node_modules" "$ADVISOR_DIR/node_modules" | |
| - name: Run E2E recommendation advisor | |
| id: analysis | |
| continue-on-error: true | |
| env: | |
| BASE_REF: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || (github.event_name == 'workflow_dispatch' && inputs.target_repo != '' && inputs.target_pr != '' && format('target/{0}', inputs.target_base) || inputs.base_ref) }} | |
| HEAD_REF: ${{ github.event_name == 'pull_request' && 'HEAD' || (github.event_name == 'workflow_dispatch' && inputs.target_repo != '' && inputs.target_pr != '' && 'HEAD' || inputs.head_ref) }} | |
| E2E_ADVISOR_RUN_ANALYSIS: ${{ github.event_name == 'workflow_dispatch' && inputs.run_analysis == false && '0' || '1' }} | |
| # Preferred E2E advisor secret. | |
| E2E_ADVISOR_API_KEY: ${{ secrets.PI_E2E_ADVISOR_API_KEY }} | |
| # Optional local-provider-compatible fallback for future upstream/external-advisor use. | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| run: | | |
| cd "$ADVISOR_WORKDIR" | |
| node --experimental-strip-types "$ADVISOR_DIR/tools/e2e-advisor/analyze.mts" \ | |
| --base "$BASE_REF" \ | |
| --head "$HEAD_REF" \ | |
| --schema "$ADVISOR_DIR/tools/e2e-advisor/schema.json" \ | |
| --out-dir "$GITHUB_WORKSPACE/artifacts/e2e-advisor" | |
| - name: Run scenario E2E advisor | |
| id: scenario-analysis | |
| continue-on-error: true | |
| env: | |
| BASE_REF: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || (github.event_name == 'workflow_dispatch' && inputs.target_repo != '' && inputs.target_pr != '' && format('target/{0}', inputs.target_base) || inputs.base_ref) }} | |
| HEAD_REF: ${{ github.event_name == 'pull_request' && 'HEAD' || (github.event_name == 'workflow_dispatch' && inputs.target_repo != '' && inputs.target_pr != '' && 'HEAD' || inputs.head_ref) }} | |
| E2E_SCENARIO_ADVISOR_RUN_ANALYSIS: ${{ github.event_name == 'workflow_dispatch' && inputs.run_analysis == false && '0' || '1' }} | |
| # Reuse the shared E2E advisor secret. The scenario advisor is a | |
| # separate prompt/agent but uses the same model and credential. | |
| E2E_ADVISOR_API_KEY: ${{ secrets.PI_E2E_ADVISOR_API_KEY }} | |
| # Optional local-provider-compatible fallback for future upstream/external-advisor use. | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| run: | | |
| cd "$ADVISOR_WORKDIR" | |
| node --experimental-strip-types "$ADVISOR_DIR/tools/e2e-advisor/scenarios.mts" \ | |
| --base "$BASE_REF" \ | |
| --head "$HEAD_REF" \ | |
| --schema "$ADVISOR_DIR/tools/e2e-advisor/scenarios-schema.json" \ | |
| --out-dir "$GITHUB_WORKSPACE/artifacts/e2e-advisor" | |
| - name: Publish job summary | |
| if: always() | |
| run: | | |
| if [ -f "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-summary.md" ]; then | |
| cat "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-summary.md" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| printf '# E2E Recommendation Advisor\n\nAdvisor analysis did not produce a summary. See raw artifacts/logs.\n' >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| if [ -f "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-scenario-advisor-summary.md" ]; then | |
| cat "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-scenario-advisor-summary.md" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Auto-dispatch required E2E jobs | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| # Optional comma-separated GitHub login allowlist for trusted authors whose | |
| # private org membership appears as CONTRIBUTOR in pull_request payloads. | |
| E2E_ADVISOR_AUTO_DISPATCH_ALLOWED_AUTHORS: ${{ secrets.E2E_ADVISOR_AUTO_DISPATCH_ALLOWED_AUTHORS }} | |
| run: | | |
| DISPATCH_RESULT="$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-dispatch-result.json" | |
| DISPATCH_SUMMARY="$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-dispatch-summary.md" | |
| if [ -f "$ADVISOR_DIR/tools/e2e-advisor/dispatch.mts" ]; then | |
| node --experimental-strip-types "$ADVISOR_DIR/tools/e2e-advisor/dispatch.mts" \ | |
| --result "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-final-result.json" \ | |
| --workflow nightly-e2e.yaml \ | |
| --workflow-path "$ADVISOR_DIR/.github/workflows/nightly-e2e.yaml" \ | |
| --out-dir "$GITHUB_WORKSPACE/artifacts/e2e-advisor" | |
| else | |
| echo "Skipping E2E auto-dispatch: trusted main checkout does not yet contain dispatch.mts" | |
| mkdir -p "$GITHUB_WORKSPACE/artifacts/e2e-advisor" | |
| printf '{\n "status": "skipped",\n "reason": "trusted main checkout does not yet contain dispatch.mts",\n "workflow": "nightly-e2e.yaml"\n}\n' > "$DISPATCH_RESULT" | |
| printf '# E2E Advisor Auto-dispatch\n\nStatus: **skipped**\nReason: trusted main checkout does not yet contain dispatch.mts\nWorkflow: `nightly-e2e.yaml`\n' > "$DISPATCH_SUMMARY" | |
| fi | |
| if [ -f "$DISPATCH_SUMMARY" ]; then | |
| cat "$DISPATCH_SUMMARY" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Post E2E advisor PR comment | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.E2E_ADVISOR_GITHUB_TOKEN || github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| COMMENT_ARGS=( | |
| --repo "$GITHUB_REPOSITORY" | |
| --pr "$PR_NUMBER" | |
| --summary "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-summary.md" | |
| --result "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-final-result.json" | |
| --dispatch "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-advisor-dispatch-result.json" | |
| ) | |
| if [ -f "$ADVISOR_DIR/tools/e2e-advisor/comment.mts" ]; then | |
| node --experimental-strip-types "$ADVISOR_DIR/tools/e2e-advisor/comment.mts" "${COMMENT_ARGS[@]}" | |
| elif [ -f "$ADVISOR_DIR/tools/e2e-advisor/comment.mjs" ]; then | |
| node "$ADVISOR_DIR/tools/e2e-advisor/comment.mjs" "${COMMENT_ARGS[@]}" | |
| else | |
| echo "Skipping E2E advisor comment: trusted main checkout does not contain comment.mts or comment.mjs" | |
| fi | |
| - name: Post E2E scenario advisor PR comment | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.E2E_ADVISOR_GITHUB_TOKEN || github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if [ -f "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-scenario-advisor-summary.md" ] && [ -f "$ADVISOR_DIR/tools/e2e-advisor/scenario-comment.mts" ]; then | |
| node --experimental-strip-types "$ADVISOR_DIR/tools/e2e-advisor/scenario-comment.mts" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --pr "$PR_NUMBER" \ | |
| --summary "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-scenario-advisor-summary.md" \ | |
| --result "$GITHUB_WORKSPACE/artifacts/e2e-advisor/e2e-scenario-advisor-result.json" | |
| else | |
| echo "Skipping E2E scenario advisor comment: summary or scenario-comment.mts missing" | |
| fi | |
| - name: Upload advisor artifacts | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: e2e-advisor | |
| path: artifacts/e2e-advisor/ | |
| if-no-files-found: warn |