Skip to content

fix(cli): report inference health from a served request - #8731

Merged
prekshivyas merged 20 commits into
mainfrom
fix/inference-health-truthfulness
Aug 13, 2026
Merged

fix(cli): report inference health from a served request#8731
prekshivyas merged 20 commits into
mainfrom
fix/inference-health-truthfulness

Conversation

@laitingsheng

@laitingsheng laitingsheng commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

nemoclaw <name> status reported inferenceHealth.ok: true and exited 0 while the in-sandbox inference route answered HTTP 401, and start exited 0 on the same route. The route probe reads any final HTTP 200-499 as reachable, so a rejected provider credential passed it. Status and start now send one inference request over the same route and report its result, so a rejected credential reports unauthorized and exits nonzero.

Related Issue

Fixes #8705

Changes

  • Rename src/lib/actions/sandbox/rebuild-inference-preflight.ts to inference-invocation-probe.ts and its exports to buildSandboxInferenceInvocationCommand and probeSandboxInferenceInvocation. The module already sent one minimal completion through inference.local on the sandbox's stored credential, but only Deep Agents Code rebuild preflight consumed it. Status and start are the second and third consumers, so the name now describes the behaviour rather than the first caller. The failure result adds httpStatus, and the timeout is a parameter: rebuild keeps 100 seconds, status and start use 30.
  • Compose inference health in src/lib/actions/sandbox/inference-route-health.ts. When the route probe reports the route reachable, status sends one inference request and its result drives inferenceHealth.ok; HTTP 401 or 403 sets failureLabel: "unauthorized". The route probe result becomes a route reachability subprobe so a failing verdict still shows that the route itself answered. A host-side upstream probe stays a diagnostic and does not change ok, keeping the sandbox route authoritative.
  • Gate startSandbox readiness on the same request inside verifyStarted, after the existing gateway and forward checks, and exit 1 when it fails. A sandbox with no recorded provider or model has nothing to request, so start skips it and exits 0.
  • Take the request's provider and model as one pair, preferring the live gateway route and falling back to the recorded route, so a partially readable live route cannot pair a live model with a recorded provider.
  • Update docs/reference/commands.mdx, including the Inference state table, and the pages whose acceptance criteria or failing-state lists this change invalidates: docs/inference/choose-local-inference-server.mdx, docs/inference/set-up-llama-cpp.mdx, docs/inference/set-up-ollama.mdx, docs/inference/set-up-vllm-on-two-dgx-stations.mdx, docs/deployment/deploy-to-headless-server.mdx, docs/monitoring/monitor-sandbox-activity.mdx, docs/reference/troubleshooting.mdx, and docs/security/credential-rotation.mdx.
  • Update the inference health entries in .agents/skills/_shared/controlled-words.md, which defined the term as the /v1/models classification alone.

Contract changes maintainers should weigh

  • This reverses part of nemoclaw status reports inference healthy on endpoint reachability, not model invocability — a green status can mask an unreachable model #6846. Two tests asserted the reported behaviour and are rewritten: the unit test expecting { ok: true, okLabel: "reachable" } beside a failed unauthorized subprobe, and the CLI test named "treats an inference.local HTTP %s as healthy", which asserted exit 0 for 401 and 403.
  • status and start now send a 16-token request through the stored provider credential on every run, so both wait up to 30 seconds for it and consume provider tokens on a hosted route.
  • doctor is unchanged and still classifies an HTTP 401 or 403 route response as reachable, so it exits 0 where status now reports unauthorized. The difference is documented; making doctor consistent needs its own change.
  • The issue also asks for phase to stop reporting Ready when inference is broken. phase is OpenShell's own container lifecycle value, parsed in src/lib/state/gateway.ts and shared with TERMINAL_SANDBOX_PHASES, so this change leaves it alone and reports the condition through inferenceHealth and the exit codes instead.
  • The serving-process gap in status inference health can only attest a fresh exec's environment, never the serving process's — surface process-level (self-reported) health #7003 remains open. The request runs in a fresh sandbox exec, so it attests the route and credential, not the long-running gateway process's own environment.

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:
  • 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: fix(cli): report inference health from a served request #8731 (comment)
  • 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: docs-updated
  • Evidence: docs/reference/commands.mdx, docs/inference/verify-inference-route.mdx, docs/inference/choose-local-inference-server.mdx, docs/inference/set-up-llama-cpp.mdx, docs/inference/set-up-ollama.mdx, docs/inference/set-up-vllm-on-two-dgx-stations.mdx, docs/deployment/deploy-to-headless-server.mdx, docs/monitoring/monitor-sandbox-activity.mdx, docs/reference/troubleshooting.mdx, docs/security/credential-rotation.mdx, .agents/skills/_shared/controlled-words.md
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

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 or justification: npx vitest run src/lib/actions/sandbox/status-snapshot-inference-health.test.ts src/lib/actions/sandbox/status-inference.test.ts src/lib/actions/sandbox/start.test.ts src/lib/actions/sandbox/inference-invocation-probe.test.ts src/lib/actions/sandbox/status-snapshot-recovery.test.ts — 5 files, 71 tests passed; npx vitest run test/cli — 35 files, 283 tests passed; the new CLI cases fail against a pre-fix build with expected +0 to be 1; follow-up rebuild harness validation passed 2 files and 9 tests after the probe rename; the runtime-recovery compatibility test and its related CLI suites passed 3 files and 29 tests after the fixture learned the agent-request probe
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • 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)

Signed-off-by: Tinson Lai tinsonl@nvidia.com

Summary by CodeRabbit

  • New Features

    • Sandbox inference health checks now perform an actual inference request after route verification.
    • Status output distinguishes healthy, unauthorized, unreachable, unhealthy, reachable-only, and unverified results.
    • Sandbox startup validates inference readiness when provider and model details are available.
    • Diagnostics include request failures, HTTP status details, token usage, and authentication guidance.
  • Documentation

    • Updated deployment, setup, monitoring, troubleshooting, security, and command references to explain the new health-check behavior and statuses.

The in-sandbox route probe reads any final HTTP 200-499 as reachable, so a
rejected provider credential answered 401 while status reported healthy
inference and exited 0. Status and start now send one inference request over
the same route and report its result, and start exits nonzero when the
request fails.

Signed-off-by: Tinson Lai <tinsonl@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Sandbox inference health now combines route reachability with an authenticated inference request. Startup readiness, status output, failure labels, diagnostics, tests, and documentation now distinguish healthy, unauthorized, unreachable, and unhealthy results.

Changes

Sandbox inference health

Layer / File(s) Summary
Sandbox invocation probe API
src/lib/actions/sandbox/inference-invocation-probe.ts, src/lib/actions/sandbox/inference-invocation-probe.test.ts
Renames the probe API for sandbox inference invocation. Results now include HTTP status details and configurable timeouts.
Route-health composition
src/lib/actions/sandbox/inference-route-health.ts
Combines gateway reachability, invocation results, and provider diagnostics. Invocation failures receive structured labels such as unauthorized, unhealthy, and unreachable.
Status snapshot integration and coverage
src/lib/actions/sandbox/status-snapshot.ts, src/lib/actions/sandbox/status-inference.test.ts, src/lib/actions/sandbox/status-snapshot-inference-health.test.ts, src/lib/actions/sandbox/status-snapshot-recovery.test.ts
Status selects a provider/model pair, invokes the sandbox route after gateway checks, and preserves route and provider diagnostics as subprobes.
Startup and rebuild wiring
src/lib/actions/sandbox/start.ts, src/lib/actions/sandbox/start.test.ts, src/lib/actions/sandbox/rebuild-dcode-preflight.ts
Startup performs a timed inference request after gateway verification. Failed requests return a nonzero result. Rebuild checks use the shared invocation probe.
CLI status validation
test/cli/helpers.ts, test/cli/sandbox-status-json.test.ts, test/cli/sandbox-status-text.test.ts
Shared stubs now simulate inference responses. CLI tests cover healthy, unauthorized, reachable, and unprobed states.
Documentation and terminology
.agents/skills/_shared/controlled-words.md, docs/deployment/deploy-to-headless-server.mdx, docs/inference/*.mdx, docs/monitoring/monitor-sandbox-activity.mdx, docs/security/credential-rotation.mdx, docs/reference/commands.mdx, docs/reference/troubleshooting.mdx
Documentation defines inference health through route probing and an applicable inference request. Verification and troubleshooting guidance use the updated statuses.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant statusSnapshot
  participant Gateway
  participant SandboxInference
  participant RouteHealth
  User->>statusSnapshot: request sandbox status
  statusSnapshot->>Gateway: probe inference route
  Gateway-->>statusSnapshot: route reachability
  statusSnapshot->>SandboxInference: send authenticated inference request
  SandboxInference-->>statusSnapshot: response and HTTP status
  statusSnapshot->>RouteHealth: combine route and invocation results
  RouteHealth-->>User: request-aware inference health
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR fixes inference health and start exit codes, but it leaves the lifecycle phase as Ready after inference failure, missing a key requirement in issue #8705. Update readiness or phase reporting so an unauthorized or failed inference request cannot leave the sandbox in phase Ready.
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The code, tests, and documentation changes directly support inference health, readiness checks, authorization failures, and the linked issue.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reporting inference health based on a served inference request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/inference-health-truthfulness

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

@laitingsheng laitingsheng added area: cli Command line interface, flags, terminal UX, or output area: inference Inference routing, serving, model selection, or outputs bug-fix PR fixes a bug or regression labels Aug 10, 2026
@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 · high 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 — inference health at .agents/skills/_shared/controlled-words.md:187: Use inference health for the complete classification. Use inference route reachability for the /v1/models result alone.
  • established — route reachability at docs/reference/commands.mdx:1678: Use route reachability only for the /v1/models subprobe. Use inference health for the inference-request verdict.
  • justified — unauthorized at docs/reference/commands.mdx:1737: Use unauthorized for HTTP 401 or 403 inference-request failures and direct operators to refresh the provider credential.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite for the commit under review.

Recommended E2E: managed-image-protected-runtime, inference-routing

Manual-only E2E: managed-image-multiarch-startup, network-policy, onboard-repair, onboard-resume, rebuild-openclaw, state-backup-restore, ubuntu-repo-docker-post-reboot-recovery
The manual PR workflow does not run these selectors for the commit under review. Run them from reviewed code on main.

1 optional E2E recommendation
  • ollama-auth-proxy

Workflow run details

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

@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.

@github-code-quality

github-code-quality Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit a36cc95 in the fix/inference-health... branch remains at 96%, unchanged from commit 7c721ae in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit a36cc95 in the fix/inference-health... branch remains at 82%, unchanged from commit 7c721ae in the main branch.

Show a code coverage summary of the most impacted files.
File main 7c721ae fix/inference-health... a36cc95 +/-
src/lib/cua/bounded-file.ts 94% 84% -10%
src/lib/cua/run...ime-manifest.ts 91% 84% -7%
src/lib/inference/health.ts 93% 87% -6%
src/lib/inferen...ollama/proxy.ts 36% 33% -3%
src/lib/state/o...d-checkpoint.ts 90% 87% -3%
src/lib/onboard...press-resume.ts 78% 82% +4%
src/lib/securit...ntial-filter.ts 91% 95% +4%
src/lib/onboard...der/snapshot.ts 75% 83% +8%
src/lib/actions...ocker-health.ts 61% 78% +17%
src/lib/actions...cation-probe.ts 0% 97% +97%

Updated August 12, 2026 21:25 UTC

@github-actions

Copy link
Copy Markdown
Contributor

@apurvvkumaria apurvvkumaria self-assigned this Aug 11, 2026
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Maintainer decision required before readiness work continues

This PR changes the product contract in ways that need explicit maintainer direction:

  • Every status and applicable start call would send a live 16-token inference request, potentially adding provider cost and up to 30 seconds of latency.
  • It intentionally reverses part of nemoclaw status reports inference healthy on endpoint reachability, not model invocability — a green status can mask an unreachable model #6846 while leaving doctor with different health semantics.
  • The linked issue asks readiness not to remain Ready when inference is broken, but this PR leaves the OpenShell lifecycle phase unchanged. Accepting that split requires a scope and acceptance decision.
  • The current advisor warning also identifies a correctness risk when live provider/model data is combined with a recorded API family; that needs remediation if the proposed contract is accepted.

The sensitive-path and documentation receipts must also be refreshed for the current branch revision after the direction is settled. I am deferring implementation, approval, and merge because resolving the contract requires product and architecture judgment.

@prekshivyas prekshivyas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking correctness finding on latest PR commit 4a5cbca0d.

status-snapshot.ts now takes the provider/model pair from the usable live gateway route, but still passes sb?.preferredInferenceApi from the recorded sandbox into runSandboxInferenceInvocationProbe. When live route drift changes the API family, status can send the wrong request shape/endpoint and report an otherwise usable route as unhealthy.

Derive provider, model, and API family from one route authority. If the live route is selected, use its compatible API family; otherwise use the complete recorded route contract. Add a status regression where the recorded route is Responses-based and the live route requires Chat Completions, then assert the served request uses the live route's endpoint and reports healthy.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas
prekshivyas dismissed their stale review August 11, 2026 18:36

Addressed by a4b22d8: live and recorded inference routes now keep provider/model/API coherent; 13 focused status-inference tests and the exact-head pre-push typecheck passed.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Security review completed for the current #8731 revision.

Scope:

  • the in-sandbox inference request used by status, start, and DCode rebuild preflight
  • live-versus-recorded route selection
  • command construction, credential handling, timeouts, failure classification, and test-harness updates

Result: no actionable security finding.

The request uses the fixed https://inference.local origin. Provider and model values are encoded as JSON and shell-quoted. The command does not include a host credential, and it retains only the HTTP status. Status and start use a bounded 30-second execution timeout. Rebuild retains its existing 100-second allowance. The live provider, model, and compatible API family now come from one route authority, which resolves the prior request-shape finding.

Validation on commit 0c6b9a6ad:

  • 9 affected rebuild integration tests passed
  • 36 inference-focused CLI tests passed
  • CLI build and both TypeScript checks passed
  • repository structure checks and commit hooks passed

Fresh repository CI is running. The prior review request is resolved by the contributor's two route-coherence commits. The follow-up harness commit repairs the module rename that caused the earlier CI shard failures.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

The failed CLI recovery check was caused by its OpenShell fixture not answering the new inference invocation. Revision 282a38b updates that fixture without changing production behavior. Five source-test files passed 84 tests, and three CLI integration files passed 29 tests. The Station pair-preparation failure was unrelated to the inference changes and passed twice in isolation, so the new revision provides a safe fresh CI run. The signed commit is Verified.

@prekshivyas prekshivyas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking inference-health correctness finding on latest PR commit 2bf953663.

src/lib/actions/sandbox/status-snapshot.ts:567-576 clears preferredInferenceApi whenever the live RPC returns a provider/model pair, including when that pair exactly matches the recorded sandbox route. For a compatible endpoint recorded with preferredInferenceApi: "openai-responses", status therefore discards the only API-family metadata and probes the default Chat Completions path, producing a false unhealthy result against a Responses-only endpoint. The new test covers a changed live route, but not the unchanged-pair case.

Please retain the recorded API family when the live provider/model pair exactly equals the recorded pair, and clear it only when the pair has drifted (or expose the API family through the live RPC). Add a same-pair Responses regression that asserts /v1/responses is probed.

Security rubric: all nine categories PASS; this is a correctness blocker rather than a security vulnerability. Files reviewed: the complete 27-file diff.

github-actions Bot and others added 4 commits August 12, 2026 05:48
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Addressed the unchanged-route inference-health blocker and refreshed the branch from current main.

  • When the live provider/model pair matches the recorded route, status now retains the recorded API family. A Responses-based route is probed through /v1/responses.
  • When the live pair differs, the recorded API family is still discarded so route drift cannot select an incompatible request shape.
  • Added a same-route Responses regression and retained the existing different-route Chat Completions regression.
  • Updated the command and route-verification docs to match served-request health, unauthorized responses, aligned-route API selection, and drift behavior.

Validation on the pushed revision:

  • 25 focused CLI tests passed.
  • CLI build and type check passed.
  • Repository hooks, source-shape, and test-size gates passed.
  • Documentation generation and validation completed with 0 errors; the two notices are Fern upgrade availability warnings.
  • The pushed commits are signed, include DCO sign-offs, and GitHub reports the latest commit as verified.

Security review remains clean across the nine-category rubric. The change preserves the route-authority boundary by retaining API-family metadata only for an unchanged provider/model pair. The effective PR diff is 27 files with 730 changed lines, below the large-change threshold used in this readiness pass.

Fresh repository checks and re-review are now pending. I did not change reviewer routing.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

The failed CLI shard exposed a stale documentation-contract assertion after the reviewed wording change. Updated that assertion to match the existing source page.

Validation:

  • focused documentation contract: 23 tests passed
  • repository hooks and pre-push CLI type-check passed
  • documentation writer follow-up: no additional documentation change needed because the source page already describes the two-stage health check
  • latest pushed commit is Verified and retains its DCO sign-off

Fresh checks and human re-review are pending. Reviewer routing was not changed.

@cv
cv dismissed prekshivyas’s stale review August 12, 2026 18:38

The latest PR commit retains the recorded API family when the live route matches and tests the Responses endpoint. Dismissing this resolved review before a full current-commit review.

@apurvvkumaria

Copy link
Copy Markdown
Collaborator

Addressed the response-validation blocker on this branch.

  • The readiness probe now captures at most 64 KiB with restrictive temporary-file permissions and removes the file on exit.
  • It validates the configured Chat Completions, Responses, or Anthropic response shape; empty, malformed, error-envelope, and mismatched 2xx responses fail closed.
  • The probe reports fixed diagnostics without exposing the response body or credential material.
  • Validation passed for 133 focused tests, CLI type checking, applicable repository hooks, the documentation build, route generation, agent-variant synchronization, and the source-architecture budget.
  • Documentation review updated the inference verification and command-reference pages. A focused security review found no new credential, injection, authorization, dependency, or response-disclosure issue.

Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@apurvvkumaria

Copy link
Copy Markdown
Collaborator

The failed CLI shards were PR-related: their status and recovery fixtures returned a successful HTTP status without a response body, which the new readiness validation correctly rejects.

Updated the shared fixture to return structurally valid bodies for Chat Completions, Responses, and Anthropic Messages, and aligned the recovery fixture. The five affected test files now pass locally (58 tests), CLI type-checking passes, repository hooks pass, and the documentation follow-up confirmed no additional docs change is needed. The earlier local loopback-listener error was environmental; the same tests passed with their required host access.

@prekshivyas prekshivyas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head, served-request health behavior across status/start/rebuild, required CI, and advisor feedback. Required checks are green and no blocking findings remain.

@prekshivyas
prekshivyas merged commit 6f478d6 into main Aug 13, 2026
79 of 88 checks passed
@prekshivyas
prekshivyas deleted the fix/inference-health-truthfulness branch August 13, 2026 04:18
prekshivyas pushed a commit that referenced this pull request Aug 17, 2026
…9307)

<!-- markdownlint-disable MD041 -->
## Summary

Sandbox status probes the live shared inference route with one real
inference request, and that request's result decides `status --json`'s
exit code. The probe dropped the sandbox's recorded API family whenever
the live route was not exactly aligned — including when the shared route
drifted by **model alone** and the provider was unchanged. For a
compatible endpoint that family is the only signal that the route speaks
`openai-responses` or `anthropic-messages`, so a healthy route was
probed with the wrong API and reported unhealthy, making the command
exit nonzero.

Refs #9302.

## Reproduction

**Environment**

- Test machine: our Ubuntu 24.04 x86_64 test host (no GPU)
- NemoClaw `main` at `588bb6db9b1132266840e4604fa16c2a4912cbfc`
(`v0.0.109-96-g588bb6db9`), OpenShell CLI 0.0.101
- Sandbox: OpenClaw, provider NVIDIA Endpoints (`nvidia-prod`)
- The reporter's platform is Ubuntu 26.04 GPU; this path is CLI/JSON
exit-code logic with no GPU or OS-version dependency, but see **Platform
scope** below.

First, what already works on `main`. Recorded route
`nvidia-prod/nvidia/nemotron-3-ultra-550b-a55b`; the shared route was
changed externally with `openshell inference set` to another route that
genuinely serves:

```text
### drift to a healthy route
  exit=0 | drift=yes | live=openai/gpt-oss-20b | inferenceHealth.ok=True | recordedRoute/liveRoute/routeDrift all present
```

Route drift on its own already exits `0` and already returns all three
documented fields, so the drift itself is not the trigger.

The trigger is the API family. With the recorded route on a compatible
endpoint that speaks `openai-responses`, and the shared route drifting
by model only, the probe on `main` is handed no API family at all:

```json
{ "provider": "compatible-endpoint", "model": "live/model", "preferredInferenceApi": null }
```

`getSandboxInferenceConfig` resolves `null` to `openai-completions`,
which for that provider is the wrong endpoint:

```text
compatible-endpoint  recorded=openai-responses     kept=openai-responses     dropped=openai-completions   <-- family changes
compatible-endpoint  recorded=anthropic-messages   kept=anthropic-messages   dropped=openai-completions   <-- family changes
compatible-endpoint  recorded=openai-completions   kept=openai-completions   dropped=openai-completions
nvidia-prod          recorded=openai-responses     kept=openai-completions   dropped=openai-completions
```

The request then fails against a route that is actually healthy,
`inferenceHealth.ok` goes false, and `status --json` exits `1`.

**Observed on `main` (before fix)** — invocation probe input for a
model-only drift on a compatible endpoint:

```json
{ "provider": "compatible-endpoint", "model": "live/model", "preferredInferenceApi": null }
```

**Observed on `fix/...` (after fix)**

```json
{ "provider": "compatible-endpoint", "model": "live/model", "preferredInferenceApi": "openai-responses" }
```

Live re-verification on the test host, with the same sandbox, after the
fix:

```text
### drift to a healthy route      -> exit=0 | inferenceHealth.ok=True  | all three fields present
### route aligned                 -> exit=0 | inferenceHealth.ok=True  | all three fields present
### drift to an unusable route    -> exit=1 | inferenceHealth.ok=False | all three fields present
```

The third row is unchanged on purpose — see **Scope** below.

## Analysis

`src/lib/actions/sandbox/status-snapshot.ts` builds the route for the
in-sandbox invocation probe. It takes provider and model as one pair
from the live gateway route, which #8731 introduced deliberately, and
then set:

```ts
preferredInferenceApi:
  routeDriftPlan?.kind === "aligned" ? (sb?.preferredInferenceApi ?? null) : null,
```

`aligned` means provider **and** model both match. The stated reason for
dropping the family is sound but only covers the provider case: a family
recorded for one provider must not be carried onto a different provider,
because `getSandboxInferenceConfig` would otherwise route e.g. a
persisted `openai-responses` onto a provider with no `/v1/responses`
endpoint and 404 every request — the same hazard its own comment
describes.

That reason does not hold when only the model drifted. The recorded
family describes the recorded *provider*, and that provider is
unchanged, so the family still describes the live route exactly.
Dropping it there falls back to `openai-completions` and probes an
endpoint the provider does not serve, so a healthy route is reported
unhealthy and the command exits nonzero.

Built-in providers hide this: `getSandboxInferenceConfig` forces the
family from the provider for `anthropic-prod` and for every provider
matching `shouldSkipResponsesProbe`, so `nvidia-prod` resolves to
`openai-completions` either way. Only providers whose family is carried
in the sandbox record — the compatible endpoints — are affected.

## Fix

Gate on the provider rather than on full alignment:

```diff
-  routeDriftPlan?.kind === "aligned" ? (sb?.preferredInferenceApi ?? null) : null,
+  live.provider === sb?.provider ? (sb?.preferredInferenceApi ?? null) : null,
```

This is strictly wider than `aligned` (which already required the
provider to match), so the aligned case is unchanged and the
cross-provider guard is preserved verbatim: the family is still dropped
whenever the provider itself drifted.

Three tests enforce the contract: the recorded API family survives a
model-only drift and an aligned route, while a provider change removes
it. The first test fails against the earlier logic and passes after the
fix.

## Scope

This PR uses `Refs`, not `Fixes`, because it covers one of two ways the
reported symptom can arise and I could not confirm which one the
reporter hit.

Measured on `main`, `status --json` exits nonzero after a shared-route
change in exactly two situations:

1. **The live route is healthy but probed with the wrong API family.**
That is the defect this PR fixes.
2. **The live route is genuinely unusable** (for example the shared
route was repointed at a placeholder model to manufacture the mismatch).
There `inferenceHealth.ok` is legitimately false and the nonzero exit is
the contract #8731 established for #8705 — status sends one real
inference request and reports its result.

Worth noting for the second case: the exit code does not withhold the
payload. In every failing run above, stdout still carried the complete
JSON document with `recordedRoute`, `liveRoute` and `routeDrift`
populated, so a caller can read the drift fields even when the command
exits `1`. If the validation job treats a nonzero exit as "no JSON to
parse", it will report the fields as unavailable when they are in fact
present.

To confirm whether case 1 is what the pipeline hit, the reporter's
provider and `--inference-api` for the sandbox under test, plus the
captured stdout of the failing `status --json`, would settle it.

## Platform scope

Reproduced and verified on our Ubuntu 24.04 x86_64 test host; the
reporter's runner is Ubuntu 26.04 GPU. The changed code is
provider/API-family resolution in the status snapshot, with no GPU,
kernel, or OS-version dependency, but this was not re-run on Ubuntu
26.04.

## Changes

- `src/lib/actions/sandbox/status-snapshot.ts`: keep the recorded API
family while the live provider matches the recorded provider; drop it
only on a provider change.
- `src/lib/actions/sandbox/status-snapshot-route-drift.test.ts`: cover
model-only drift, aligned routes, and provider changes at the public
health-result boundary.
- `docs/reference/commands.mdx` and
`docs/inference/verify-inference-route.mdx`: describe API-family
selection during route drift.

## Type of Change

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

## Verification

- [x] `npx prek run` passes on the changed files
- [x] `npm test` passes (3105 tests across `src/lib/actions/sandbox/`
and `test/cli/sandbox-status-json.test.ts`)
- [x] Tests added or updated for new or changed behavior
- [x] Focused route-drift tests pass (11 of 11)
- [x] No secrets, API keys, or credentials committed
- [x] Docs updated for the user-facing route-validation behavior
- [x] `npm run docs` passes with 0 errors and 2 existing warnings
- [x] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## Documentation Writer Review

- [x] Documentation writer subagent reviewed the completed changes
- Result: `docs-updated`
- Evidence: `docs/reference/commands.mdx` and
`docs/inference/verify-inference-route.mdx`
- Agent: Codex Desktop
<!-- docs-review-head-sha: 3f3f6cd -->
<!-- docs-review-agents-blob-sha: b9fb6a9 -->

## AI Disclosure
- [x] AI-assisted — tool: Claude Code

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved inference health checks when only the selected model changes.
- Preserved the recorded API family for the same provider, including
compatible response-based routes.
- Prevented outdated API routing from being reused when the provider
changes.
  - Health is now reported only for structurally valid responses.

- **Documentation**
- Updated sandbox status and inference route documentation to reflect
provider and model handling.

- **Tests**
  - Added regression coverage for provider and model routing changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Yanyun Liao <yanyunl@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Co-authored-by: Apurv Kumaria <akumaria@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output area: inference Inference routing, serving, model selection, or outputs bug-fix PR fixes a bug or regression

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DGX Spark][CLI&UX] inferenceHealth.ok reports true and phase Ready while inference returns HTTP 401 (false health/readiness)

4 participants