Skip to content

fix(inference): restore loopback port publishing in the llama.cpp qualification runner - #8676

Closed
AzeelSajjad wants to merge 6 commits into
NVIDIA:mainfrom
AzeelSajjad:fix/llama-cpp-qualification-publish-8667
Closed

fix(inference): restore loopback port publishing in the llama.cpp qualification runner#8676
AzeelSajjad wants to merge 6 commits into
NVIDIA:mainfrom
AzeelSajjad:fix/llama-cpp-qualification-publish-8667

Conversation

@AzeelSajjad

@AzeelSajjad AzeelSajjad commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

The DGX Spark llama.cpp qualification runner builds its server container arguments with no --publish entry, so docker port cannot resolve a loopback address and the runner cannot probe the server it just launched. This adds a qualification-only loopback mapping in the runner itself: an ephemeral 127.0.0.1::8081 by default, and the requested fixed 127.0.0.1:8081:8081 when the runner asks for a deterministic port. The shared host-local materializer and the Docker lifecycle provider are unchanged, so ordinary product launches still leave host bridging to the lifecycle provider.

Related Issue

Fixes #8667

Changes

  • scripts/checks/run-llama-cpp-dgx-spark-qualification.mts: add module-private withQualificationLoopbackPublish(), which splices one --publish <mapping> pair into the argv returned by the shared request-guard builder, immediately before the image reference. buildServerContainerArgv() wraps the shared result and forwards its optional hostPort.
  • scripts/checks/run-llama-cpp-dgx-spark-qualification.mts: drop the word ephemeral from resolveLoopbackPort()'s error message, which this change makes inaccurate on the fixed-port path.
  • test/llama-cpp-dgx-spark-qualification-runner.test.ts: pin both mapping forms, the mapping's position ahead of the image reference, and that exactly one mapping is emitted.

This adds no configuration, fallback, migration, or compatibility layer. withQualificationLoopbackPublish() is one private helper with one current consumer, buildServerContainerArgv(), and the tests named under Verification protect its contract. The hostPort option it reads already existed on buildServerContainerArgv() and is still forwarded into the shared LlamaCppHostLocalRuntimeBindings, where positiveInteger(bindings.hostPort, "llama.cpp host port", 65_535) continues to bound it.

Why the runner needs its own argument rather than a change to the shared materializer: #8615 deliberately removed publishing from the shared path because product launches delegate host bridging to the Docker lifecycle provider, which rejects a container that publishes ports (the must not publish ownership rollback from #8544). The qualification runner does not use that provider — it runs docker run directly, then docker port, then probes the resolved port. Publishing therefore belongs to the runner alone. Both emitted mapping strings are byte-identical to what the pre-#8615 materializer produced, and the runner already created its network with --internal at that time, so this restores a configuration this repository's CI is known to have run rather than introducing a new one.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: the qualification runner is CI-internal, invoked only from .github/workflows/e2e.yaml behind allow_dgx_spark_runner_queue or manual dispatch. It is not a CLI, configuration, default, or other supported product surface. A documentation writer subagent independently confirmed that no docs/ page references the script or its functions, and that every docs/ occurrence of "qualification" refers to host and platform hardware qualification or the protected qualification and activation gates, which are separate already-documented concepts.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: requesting maintainer review. The change touches the inference qualification runner. It publishes only to 127.0.0.1, adds no egress, and leaves the credential path untouched — the API key is still delivered by bind-mounted file, and the test continues to assert the key never appears on argv.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: no documentation paths changed. The reviewer searched docs/ for run-llama-cpp-dgx-spark-qualification, withQualificationLoopbackPublish, resolveLoopbackPort, and qualification, finding no page that owns this script; confirmed from .github/workflows/e2e.yaml that the runner is CI-internal; and reviewed the changed prose against WRITING.md and the controlled word list — the helper's doc comment, resolveLoopbackPort()'s amended error message, the test titles, and the commit subjects. No blocking findings.
  • Agent: Claude Code

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result:
    • npx vitest run --project integration test/llama-cpp-dgx-spark-qualification-runner.test.ts — 12/12 pass. Without this change the suite fails with expected [] to deeply equal [ '127.0.0.1::8081' ].
    • npx vitest run --project cli src/lib/inference/llama-cpp/host-local-runtime.test.ts — 16/16 pass, confirming the shared materializer still publishes nothing, including when hostPort is set.
    • npx vitest run --project cli src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts — 41/41 pass, confirming the lifecycle provider's must not publish rollback is undisturbed.
    • npm run checks:repository — all checks pass, including test-title-style.
    • npm run validate:pr — exit 0, no autofixes.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: not applicable. This change is confined to one CI-only check script and its test, and alters no runtime or test-harness behavior. For transparency: a local npm test attempt on the development host produced 37 timeout failures out of 14,702 in the cli project, all Test timed out in 5000ms under memory pressure on an 8 GB machine with cumulative setup time of 1,113 s. The affected files pass 53/53 in 13.66 s when run in isolation, and none of them import either changed file. Deferring the broad gate to CI.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Notes for Reviewers

Two items found during this work that are outside the fix and are not addressed here:

  1. The prek biome-format hook's files pattern ends in .*\.ts$, which does not match a .mts extension, while biome.json includes **/*.mts. npm run validate:pr and the prek run --from-ref invocation in .github/workflows/pr.yaml therefore both pass over formatting in every .mts file under scripts/checks/ and tools/e2e/. This branch's helper was left unformatted by that gap until it was caught by review and corrected in 7af908ab4; npm run format is now a no-op on the file. Happy to open a separate issue if maintainers agree it is worth fixing.
  2. resolveLoopbackPort() parses docker port output with /^127\.0\.0\.1:([1-9][0-9]{3,4})$/u, which accepts only 4-and-5-digit ports, while positiveInteger(contract.serve.port, ..., 65_535) permits any port from 1. A recipe declaring a shorter port would publish successfully and then fail to resolve. Not reachable today — the pinned recipe declares port: 8081 — and deliberately not changed here to keep this fix minimal.

The fixed-port availability race called out in the issue's acceptance criteria is likewise not addressed here, as the issue directs. The runner's existing pre-flight tcpOpen(plan.recipe.serve.port) refusal already narrows it to a TOCTOU window.

Signed-off-by: Azeel Sajjad aasajjad05@gmail.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved container qualification networking by ensuring required loopback access is configured reliably.
    • Added support for automatic, configured, and custom host-port mappings.
    • Resolved an issue affecting request-guard validation during qualification runs.
  • Tests

    • Expanded coverage for container argument ordering, loopback publishing, and host-port configuration scenarios.

AzeelSajjad and others added 6 commits August 9, 2026 22:07
…#8667)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sh runner strings (NVIDIA#8667)

Delete the differential --publish assertion in the agent-qualification
argv test: both operands are already pinned to exact literals by
adjacent toEqual calls, so it cannot fail independently and its stated
mutation-catching rationale was wrong (only the hostPort: 18_081 row
catches that mutation). Also collapse withQualificationLoopbackPublish's
return to Biome's canonical single-line form (biome.json includes
**/*.mts even though the pre-commit hook's regex misses it), document
what an absent vs. present hostPort produces, and drop "ephemeral" from
resolveLoopbackPort's error message since the agent-qualification path
uses a deliberately fixed port.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 39443f88-ad4b-4538-8c39-668b2a8718cf

📥 Commits

Reviewing files that changed from the base of the PR and between 5003bce and 7af908a.

📒 Files selected for processing (2)
  • scripts/checks/run-llama-cpp-dgx-spark-qualification.mts
  • test/llama-cpp-dgx-spark-qualification-runner.test.ts

📝 Walkthrough

Walkthrough

The qualification runner now adds a loopback-only Docker publish mapping before the candidate image. It supports ephemeral, configured, and custom host ports. Tests verify argument ordering, mapping count, loopback binding, and YAML-bound request-guard behavior.

Changes

Qualification loopback publishing

Layer / File(s) Summary
Qualification publish argument construction
scripts/checks/run-llama-cpp-dgx-spark-qualification.mts
The runner adds a 127.0.0.1 publish mapping after request-guard arguments. The mapping supports ephemeral or configured host ports. Validation accepts both port forms.
Publish argument validation tests
test/llama-cpp-dgx-spark-qualification-runner.test.ts
Tests verify one publish mapping before the image reference, loopback binding, port 8081, and automatic, configured, and custom host-port cases.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • NVIDIA/NemoClaw#8512 — Both PRs modify DGX Spark qualification Docker argument construction and tests for loopback publishing.
  • NVIDIA/NemoClaw#8532 — Both PRs modify qualification runner host-port construction and validation.
  • NVIDIA/NemoClaw#8591 — This PR extends request-guard command construction with loopback publishing.

Suggested labels: area: inference, platform: dgx-spark, bug-fix, v0.0.106

Suggested reviewers: cv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes restoring loopback port publishing in the llama.cpp qualification runner.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/llama-cpp-qualification-publish-8667
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · medium confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Failed after a partial review · low confidence · 0 blockers · 0 warnings · 0 suggestions

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

3 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • established — loopback bridge at scripts/checks/run-llama-cpp-dgx-spark-qualification.mts:323: Keep `loopback bridge` for the host-to-container loopback connection.
  • justified — loopback mapping at scripts/checks/run-llama-cpp-dgx-spark-qualification.mts:325: Keep `loopback mapping` to identify the no-external-interface publish constraint.
  • established — fixed loopback port at scripts/checks/run-llama-cpp-dgx-spark-qualification.mts:326: Keep `fixed loopback port` for the declared deterministic agent-qualification port.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: managed-image-multiarch-startup

1 optional E2E recommendation
  • llama-cpp-dgx-spark-qualification

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

@prekshivyas

Copy link
Copy Markdown
Collaborator

Closing as superseded by #8672, which fixed #8667 with stricter image-boundary validation, Docker publish-alias rejection, explicit port validation, and broader regression coverage. I compared the remaining delta here: the only unique code is a minor resolveLoopbackPort() error-message wording cleanup plus an additional arbitrary-host-port test; neither changes required behavior or warrants keeping this conflicting PR open. Thank you for the careful investigation and implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore loopback port publishing in the llama.cpp qualification runner

2 participants