Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9b81b72
fix(onboard): prove native Linux ARM64 GPUs with denylisted names
latenighthackathon Aug 6, 2026
39b5449
test(onboard): prove generic ARM64 GPU failure boundary
apurvvkumaria Aug 6, 2026
3598e8c
merge: refresh PR #8425 from main
apurvvkumaria Aug 6, 2026
0390806
docs(onboard): scope ARM64 GPU proof guidance
apurvvkumaria Aug 6, 2026
0831365
docs(onboard): use ARM64 CUDA proof command
apurvvkumaria Aug 6, 2026
da42422
merge(main): refresh PR #8425
apurvvkumaria Aug 6, 2026
0c69435
merge(main): refresh PR #8425
apurvvkumaria Aug 6, 2026
1619263
docs(onboard): clarify ARM64 GPU proof effects
apurvvkumaria Aug 6, 2026
4c6944a
fix(onboard): trust immutable GPU proof image
apurvvkumaria Aug 6, 2026
7444ab6
merge(main): refresh PR #8425
apurvvkumaria Aug 7, 2026
b547507
fix(docs): refresh ARM64 support citation
apurvvkumaria Aug 7, 2026
3136773
merge(main): refresh PR #8425
apurvvkumaria Aug 7, 2026
1144bf0
Merge branch 'main' into fix/native-linux-arm64-gpu-proof
cv Aug 7, 2026
ca3fcc6
Merge branch 'main' into fix/native-linux-arm64-gpu-proof
cv Aug 7, 2026
e0646e4
merge(main): refresh PR #8425
jyaunches Aug 7, 2026
03e3f18
fix(onboard): harden ARM64 GPU proof inputs
jyaunches Aug 7, 2026
456359a
fix(onboard): escape GPU names at preflight
jyaunches Aug 7, 2026
fee2a7b
merge: refresh native ARM64 GPU proof from main
apurvvkumaria Aug 7, 2026
640b6f7
merge: refresh native ARM64 GPU proof from latest main
apurvvkumaria Aug 7, 2026
f913989
merge: refresh native ARM64 GPU proof from current main
apurvvkumaria Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/platform-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
{
"name": "Windows-on-ARM GPU passthrough",
"status": "unsupported",
"notes": "Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:188`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565."
"notes": "Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:214`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565."
},
{
"name": "Non-NVIDIA GPUs (AMD/ROCm, Intel Arc, Apple Metal)",
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,13 @@ $$nemoclaw onboard --from ./Dockerfile.custom

When `$$nemoclaw onboard` detects an NVIDIA GPU on the host, it enables OpenShell GPU passthrough at both the gateway and sandbox level by default.
Detection proceeds along two paths. The `nvidia-smi`-based paths (the primary `--query-gpu=name,memory.total,memory.free` probe and the unified-memory `--query-gpu=name` fallback) require `nvidia-smi` to succeed and, on hosts whose firmware does not classify as a known NVIDIA platform (DGX Spark, DGX Station, Jetson, or Tegra), additionally require that the GPU name does not match the placeholder family observed on the Windows-on-ARM WSL2 nvidia-smi shim (`JMJWOA-Generic-*`) and that either the host is not ARM64 Linux (the observed shim is Windows-on-ARM only) or the NVIDIA kernel driver is bound (`/proc/driver/nvidia/` present), so that placeholder shims on non-NVIDIA hardware are not mistaken for real GPUs.
NemoClaw treats a recognized NVIDIA product model from `/sys/class/dmi/id/product_name` or `/sys/firmware/devicetree/base/model`, or a known Tegra device node, as authoritative platform identity.
Remove the native ARM64 proof exception when every supported host that uses it exposes a stable value through one of these signals and `detectNvidiaPlatform()` recognizes that value.
On a native or Docker Desktop-backed WSL ARM64 Linux host without firmware-confirmed NVIDIA platform metadata, the primary memory-query probe can clear a `JMJWOA-Generic-*` name only after a bounded CUDA workload succeeds.
The workload runs `docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41`.
Docker may pull the CUDA sample image from `nvcr.io` and keeps the image in the local cache after the container exits.
The names-only unified-memory fallback does not run this workload and rejects denylisted names.
Other non-firmware-vouched hosts also reject denylisted names.
Jetson/Tegra hosts that ship without `nvidia-smi` continue to be detected via the devicetree firmware fallback (`/sys/firmware/devicetree/base/model`) or the Tegra device-node fallback (`/dev/nvhost-gpu`, `/dev/nvhost-ctrl-gpu`, `/dev/nvhost-ctrl`, or `/dev/nvmap`); both bypass the trust-tier gate above.
Use `--no-gpu` to opt out when you want host-side inference providers only and do not need direct GPU access inside the sandbox.
Use `--gpu` to require GPU passthrough and fail fast if an NVIDIA GPU is not detected.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/platform-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ They are listed here so launch material, sales conversations, and support triage
| Non-Ubuntu/Debian Linux distros | Unsupported | Installer assumes `apt-get`. Fedora/Rocky/Alma/Arch/NixOS are not validated and the installer's package-manager probes do not cover them. See open issue #899 (Fedora hang). |
| Native Kubernetes or OpenShift deployments | Unsupported | NemoClaw runs the sandbox as a Docker container, not a Kubernetes pod. The default Docker-driver topology does not embed k3s. Operator-managed K8s/OpenShift deployments are out of scope; see issue #407 (community OpenShift through agent-sandbox CRD). |
| Air-gapped / offline installs | Unsupported | Onboard assumes network reachability for package fetches, container pulls, and provider validation. See open issues #4872 and #2218 (production-deployment epic covering air-gapped support, China network guidance, multi-host topology). |
| Windows-on-ARM GPU passthrough | Unsupported | Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:188`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565. |
| Windows-on-ARM GPU passthrough | Unsupported | Windows-on-ARM CPU paths run under WSL2 'tested with limitations', but GPU passthrough on WOA is denylisted (`src/lib/onboard/wsl-docker-desktop-gpu.ts:214`, `src/lib/inference/gpu-trust.test.ts:70`). See closed issue #4565. |
| Non-NVIDIA GPUs (AMD/ROCm, Intel Arc, Apple Metal) | Unsupported | Local vLLM and NIM paths assert NVIDIA CDI presence with `assertCdiNvidiaGpuSpecPresent` (`src/lib/onboard/fatal-runtime-preflight.ts`). NemoClaw does not install non-NVIDIA accelerator drivers. |
| Other LangChain, AutoGen, CrewAI, or non-listed agent harnesses | Unsupported | LangChain Deep Agents Code is the only integrated LangChain-family agent runtime (refer to the Agents section above; status `Tested`). Other LangChain agent runtimes, AutoGen, CrewAI, and any agent runtime not listed in the Agents table are not integrated. Bringing more agent runtimes is tracked as a research epic (refer to open issue #4861) but is not on the current roadmap. |
| Multi-user host sharing | Unsupported | Sandboxes are scoped to a single host user. NemoClaw treats multi-user hosts as a risk and warns at onboard; see `docs/security/openclaw-controls.mdx` Multi-user detection. |
Expand Down
23 changes: 21 additions & 2 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -831,8 +831,27 @@ The wizard cleans up stale port forwards and waits for gateway readiness automat

### GPU setup fails with a placeholder GPU name

On Windows or WSL hosts, some systems report a placeholder display adapter name even when no NVIDIA GPU firmware is present.
On Windows, WSL, and native Linux ARM64 hosts, some systems report a placeholder display adapter name even when no NVIDIA GPU firmware is present.
NVIDIA NIM and GPU-backed sandbox setup require a real NVIDIA GPU.

When the primary memory-query probe reports a placeholder name on a native or Docker Desktop-backed WSL ARM64 Linux host without firmware-confirmed NVIDIA platform metadata, onboarding runs one bounded Docker CUDA workload.
NemoClaw treats a recognized NVIDIA product model from `/sys/class/dmi/id/product_name` or `/sys/firmware/devicetree/base/model`, or a known Tegra device node, as authoritative platform identity.
Remove the native ARM64 proof exception when every supported host that uses it exposes a stable value through one of these signals and `detectNvidiaPlatform()` recognizes that value.
Docker may pull the CUDA sample image from `nvcr.io` and keeps the image in the local cache after the container exits.
The workload uses this command:

```bash
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41
```

The run is bounded to 3 minutes.
Set `NEMOCLAW_WSL_GPU_PROOF_TIMEOUT_MS` to a positive millisecond value to change that bound.
Increase the value on slow links.
A passing workload qualifies the detected GPU for GPU passthrough.
A failed or timed-out workload leaves the GPU unproven and does not enable GPU passthrough.
The names-only unified-memory fallback does not run this workload and rejects denylisted names.
WSL hosts that are not Docker Desktop-backed do not run the workload and continue to report the GPU as unavailable.

If NemoClaw rejects the detected GPU name during preflight, select a CPU or remote inference provider, or move the setup to a host with a supported NVIDIA GPU and current drivers.

Jetson/Tegra hosts support sandbox GPU passthrough through the compatibility route.
Expand Down Expand Up @@ -2712,7 +2731,7 @@ nvidia-ctk cdi list
On WSL with Docker Desktop, confirm Docker Desktop WSL integration is enabled for your distro and verify Docker GPU access from WSL:

```bash
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark
docker run --rm --gpus all nvcr.io/nvidia/k8s/cuda-sample@sha256:7c7540bdf1f942d4fb6db97069fd6c289471b54ac29e3c7fcdf914cf77af7d41
```

If GPU passthrough is not required on this host, rerun onboarding with `--no-gpu` instead.
Expand Down
17 changes: 16 additions & 1 deletion src/lib/inference/gpu-trust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ const NVIDIA_DRIVER_PROC_PATH = "/proc/driver/nvidia";
export function isDenylistedNvidiaGpuName(name: string): boolean {
return NVIDIA_GPU_NAME_DENYLIST_PATTERN.test(name);
}
/** Escape untrusted GPU names without allowing terminal-control sequences. */
export function escapeGpuNameForTerminal(value: string): string {
return [...value]
.map((character) => {
const codePoint = character.codePointAt(0) ?? 0;
const isC0 = codePoint <= 0x1f;
const isDeleteOrC1 = codePoint >= 0x7f && codePoint <= 0x9f;
const isLineSeparator = codePoint === 0x2028 || codePoint === 0x2029;
const isFormatControl = /^\p{Cf}$/u.test(character);
if (!isC0 && !isDeleteOrC1 && !isLineSeparator && !isFormatControl) return character;
return "\\u{" + codePoint.toString(16).padStart(4, "0") + "}";
})
.join("");
}

// Result of a bounded Docker `--gpus` CUDA proof. `passed` is true only when a
// real CUDA workload (not just nvidia-smi) succeeded — that is the signal that
Expand All @@ -39,7 +53,8 @@ export interface DockerGpuProofResult {

// Optional accept-path used by `detectGpu()` when an ARM64 Linux host reports a
// denylisted `JMJWOA-Generic-*` placeholder. The prover returns `null` when the
// host is not a proof candidate (not ARM64 WSL Docker Desktop), preserving the
// host is not a proof candidate (not ARM64 Linux that is native or Docker
// Desktop-backed WSL, #8096), preserving the
// #3988 fail-closed default; otherwise it returns the bounded Docker GPU proof
// outcome so a passing real GPU can be trusted without trusting the name alone.
export type Arm64WslDockerDesktopGpuProver = (gpuNames: string[]) => DockerGpuProofResult | null;
Expand Down
44 changes: 22 additions & 22 deletions src/lib/inference/nim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,13 @@ describe("nim", () => {
});

describe("detectGpu", () => {
const proveArm64WslDockerDesktopGpu = vi.fn(() => ({
passed: true,
timedOut: false,
exitCode: 0,
diagnostic: "",
}));

function withGenericLinuxFirmware(fn: () => void): void {
const fs = require("fs");
const origReadFileSync = fs.readFileSync;
Expand Down Expand Up @@ -684,7 +691,7 @@ describe("nim", () => {

try {
withFirmwareModel("Microsoft Corporation Virtual Machine", () => {
expect(nimModule.detectGpu()).toBeNull();
expect(nimModule.detectGpu({ proveArm64WslDockerDesktopGpu: null })).toBeNull();
});
} finally {
restore();
Expand All @@ -707,55 +714,48 @@ describe("nim", () => {

try {
withFirmwareModel("Microsoft Corporation Virtual Machine", () => {
expect(nimModule.detectGpu()).toBeNull();
expect(nimModule.detectGpu({ proveArm64WslDockerDesktopGpu })).toBeNull();
});
} finally {
restore();
}
});

// #4565: a real Windows-ARM N1X + WSL2 + Docker Desktop host reports the
// same `JMJWOA-Generic-*` placeholder as the Snapdragon shim, but it can
// pass a bounded Docker `--gpus` CUDA proof. When the injected prover
// confirms the proof, the denylisted name is accepted and the detection is
// tagged so the sandbox preflight reaches the Docker Desktop WSL branch.
it("accepts a denylisted ARM64 GPU when the bounded Docker GPU proof passes (#4565)", () => {
// A passing proof accepts the raw name, but terminal formatting must escape it.
it("escapes terminal controls after a denylisted ARM64 GPU proof passes (#4565)", () => {
const gpuName = "JMJWOA-Generic-\u001b\u0085\u200d\u2028\u2029GPU";
const runCapture = vi.fn((cmd: string | string[]) => {
if (!Array.isArray(cmd)) throw new Error("expected argv array");
if (cmd[0] === "nvidia-smi" && cmd.some((a: string) => a.includes("name,memory.total"))) {
return "JMJWOA-Generic-GPU, 65471, 65000\n";
return `${gpuName}, 65471, 65000\n`;
}
return "";
});
const { nimModule, restore } = loadNimWithMockedRunner(runCapture);
const proveArm64WslDockerDesktopGpu = vi.fn(() => ({
passed: true,
timedOut: false,
exitCode: 0,
diagnostic: "",
}));

try {
withFirmwareModel("Microsoft Corporation Virtual Machine", () => {
const result = nimModule.detectGpu({ proveArm64WslDockerDesktopGpu });
expect(result).toMatchObject({
type: "nvidia",
name: "JMJWOA-Generic-GPU",
name: gpuName,
count: 1,
totalMemoryMB: 65471,
wslDockerDesktopGpuProofPassed: true,
});
expect(proveArm64WslDockerDesktopGpu).toHaveBeenCalledWith(["JMJWOA-Generic-GPU"]);
expect(proveArm64WslDockerDesktopGpu).toHaveBeenCalledWith([gpuName]);
expect(nimModule.formatNvidiaGpuPreflightLines(result)).toEqual([
"NVIDIA GPU detected (JMJWOA-Generic-\\u{001b}\\u{0085}\\u{200d}\\u{2028}\\u{2029}GPU, 65471 MB)",
]);
});
} finally {
restore();
}
});

// Snapdragon WoA fail-closed: the same placeholder name, but the bounded
// CUDA proof fails because there is no usable NVIDIA device. The detection
// must stay null so #3988/#4424 is not reopened.
it("keeps rejecting a denylisted ARM64 GPU when the Docker GPU proof fails (#4565/#3988)", () => {
// A generic ARM64 host can be native Linux or Windows on ARM with WSL2.
// When the bounded CUDA proof fails, public detection must return null so
// the native Linux path does not reopen #3988/#4424.
it("rejects a denylisted generic ARM64 GPU when the Docker proof fails (#4565/#8096/#3988)", () => {
const runCapture = vi.fn((cmd: string | string[]) => {
if (!Array.isArray(cmd)) throw new Error("expected argv array");
if (cmd[0] === "nvidia-smi" && cmd.some((a: string) => a.includes("name,memory.total"))) {
Expand Down
Loading
Loading