Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d84f839
fix(onboard): continue onboarding when Homebrew cannot confirm the Op…
harjothkhara Jul 28, 2026
8350421
docs(reference): document the standalone fallback for unconfirmed Hom…
harjothkhara Jul 28, 2026
cb9d771
fix(onboard): limit the Homebrew fallback to the pinned-tap load refusal
harjothkhara Jul 28, 2026
d37a028
fix(onboard): name the pinned-tap refusal precisely in the fallback w…
harjothkhara Jul 28, 2026
3b75153
fix(onboard): keep a loaded Homebrew launchd service fatal during tap…
harjothkhara Jul 28, 2026
ab87091
fix(onboard): fail closed when the launchd probe cannot answer
harjothkhara Jul 28, 2026
48d2974
fix(onboard): treat an unrecognized launchctl failure as an unknown u…
harjothkhara Jul 28, 2026
b832b25
merge(main): sync current main
apurvvkumaria Jul 28, 2026
9fe39c1
fix(onboard): recognize only missing launchd unit
apurvvkumaria Jul 28, 2026
d7b474c
merge(onboard): preserve launchd probe hardening
apurvvkumaria Jul 28, 2026
b7f5338
merge(main): sync current main
apurvvkumaria Jul 28, 2026
c182d79
merge(main): sync current main
apurvvkumaria Jul 28, 2026
717ecb2
merge(main): sync current main
apurvvkumaria Jul 29, 2026
249b9f7
merge(main): sync current main
apurvvkumaria Jul 29, 2026
bf489d7
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cjagwani Jul 29, 2026
cdbac77
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cjagwani Jul 29, 2026
26b3b0f
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cjagwani Jul 30, 2026
eb958ff
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cjagwani Jul 30, 2026
fcc6702
merge: sync Homebrew fallback with main
cjagwani Jul 30, 2026
46debbf
merge(main): refresh #7739 against current main
cv Aug 2, 2026
3f8825b
fix(onboard): harden Homebrew fallback diagnostics
cv Aug 2, 2026
1ced5be
merge(main): refresh #7739 against current main
cjagwani Aug 3, 2026
4c1c55b
merge(main): update #7739 to latest main
cjagwani Aug 3, 2026
829b9a3
test(onboard): convert complete CRLF fixtures
cjagwani Aug 3, 2026
7920f2a
docs(onboard): record Homebrew fallback retirement
cjagwani Aug 3, 2026
5307aa8
fix(onboard): fail closed on unknown brew list errors
apurvvkumaria Aug 3, 2026
76cb83c
merge(main): update #7739 to latest main
harjothkhara Aug 4, 2026
b4b590f
merge(main): refresh Homebrew fallback
apurvvkumaria Aug 4, 2026
181e1bf
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cv Aug 4, 2026
7d258a6
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
apurvvkumaria Aug 4, 2026
47218c5
merge(main): refresh #7739 against current main
harjothkhara Aug 6, 2026
f68e951
merge(main): refresh Homebrew fallback
apurvvkumaria Aug 6, 2026
a170707
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cv Aug 7, 2026
ae595ee
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
apurvvkumaria Aug 10, 2026
c6e891b
fix(macos): preserve Homebrew lifecycle authority
prekshivyas Aug 11, 2026
9d0fc9b
docs(macos): align Homebrew lifecycle guidance
apurvvkumaria Aug 12, 2026
02cb63e
merge(main): refresh PR base
apurvvkumaria Aug 12, 2026
792fe8b
docs(macos): clarify trusted Homebrew recovery
apurvvkumaria Aug 12, 2026
7661a0e
fix(installer): require Homebrew formula trust support
apurvvkumaria Aug 13, 2026
ea5878c
merge(main): incorporate current lifecycle fixes
cv Aug 13, 2026
a8d7ecc
docs: give runnable Homebrew repair command
cv Aug 13, 2026
32f2e43
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
prekshivyas Aug 13, 2026
84be847
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
prekshivyas Aug 13, 2026
76e2263
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
prekshivyas Aug 13, 2026
787001e
fix(ci): refresh Homebrew installer trust hash
prekshivyas Aug 13, 2026
da8dacf
merge(onboard): update #7739 with main
apurvvkumaria Aug 13, 2026
ddda77d
Merge branch 'main' into fix/7707-homebrew-untrusted-tap
cv Aug 13, 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 @@ -39,7 +39,7 @@
"status": "caveated",
"prd_priority": "P0",
"ci_tested": true,
"notes": "Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight."
"notes": "Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, or when Homebrew refuses to load the official formula from its pinned tap, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight."
},
{
"name": "DGX OS (Spark)",
Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/prerequisites.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The table comes from [`ci/platform-matrix.json`](https://github.com/NVIDIA/NemoC
| DGX OS (Spark) | Docker | Tested | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| DGX OS (Station) | Docker | Tested with limitations | Tested with limitations across qualified profiles on one physical DGX Station GB300; see [Additional Setup for DGX Station](additional-setup/dgx-station-preparation) for accepted profiles, the pending no-OTA DGX OS `7.6.x` end-to-end qualification, runtime gates, and current dual-Station and dedicated CI limitations. |
| Linux | Docker | Tested | Primary tested path. Ubuntu 24.04 has host-level onboarding validation. A digest-pinned Ubuntu 26.04 userspace lane builds the CLI and runs preflight, installer, and platform contracts on eligible main pushes; Docker-host, AppArmor, Landlock, and live onboarding validation on 26.04 remain pending. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, or when Homebrew refuses to load the official formula from its pinned tap, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | Requires WSL2 with Docker Desktop backend. See [Additional Setup for Windows Machines](additional-setup/windows-preparation) before the Quickstart. |

For the complete platform support matrix, including all deferred platforms and CI coverage, refer to [Platform Support](../reference/platform-support).
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ The standalone Linux process is used only when the systemd user manager is unava
On Apple Silicon macOS, Homebrew makes the official OpenShell formula authoritative.
The installer stages the formula and onboarding starts its `openshell` service.
When Homebrew is present, a missing formula, a formula from another tap, a service-start failure, or a health failure stops onboarding.
Only a host without Homebrew uses the standalone macOS gateway fallback.
Homebrew 6.x refuses to load formulae from taps it has not marked trusted, so it can fail to confirm the official formula's identity.
When that happens, onboarding warns and uses the standalone macOS gateway fallback.
A host without Homebrew also uses the standalone macOS gateway fallback.
NemoClaw-managed gateways on custom ports remain detached and separate from the default service.
An externally supervised gateway can use any matching configured port; its declared supervisor retains lifecycle authority.
In both Docker-driver modes, the sandbox is a Docker container, not a Kubernetes pod.
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 @@ -81,7 +81,7 @@ For install requirements and the shorter setup-oriented platform view, refer to
| DGX OS (Spark) | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). |
| DGX OS (Station) | Docker | Tested with limitations | P1 | No | The PRD marks this platform as P1. Physical validation on one DGX Station GB300 covers generic Ubuntu 24.04 ARM64, stock DGX OS `7.5.0`, the April 2026 NVIDIA Colossus BaseOS profile, and the June 2026 NVIDIA AI Developer Tools profile. A physical no-OTA DGX OS `7.6.0` host provided the release and hardware profile used for its stable workstation-family classifier and passed read-only eligibility and runtime-command preflight. Full Station Express end-to-end qualification for the accepted no-OTA DGX OS `7.6.x` profile is pending. The profile remains subject to the same physical GB300, driver, ECC, Docker, CDI, and container GPU validation. Clean-host end-to-end validation passed on generic Ubuntu and Colossus BaseOS; stock DGX OS and AI Developer Tools completed Station Express validation. The DGX OS `7.5.0` run used released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. A dual-Station configuration has not been validated, and dedicated CI coverage is not available. Direct-GPU policies expose only the exact read-only BDF directory for each discovered display-class PCI device with NVIDIA vendor ID (`0x10de`) and GB300 device ID (`0x31c2` or `0x31c3`) plus required existing topology and module paths; they do not expose `/sys`, the PCI parent subtree, or sysfs write access. During physical validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the explicit temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/additional-setup/dgx-station-preparation). |
| Linux | Docker | Tested | P0 | Yes | Primary tested path. Ubuntu 24.04 has host-level onboarding validation. A digest-pinned Ubuntu 26.04 userspace lane builds the CLI and runs preflight, installer, and platform contracts on eligible main pushes; Docker-host, AppArmor, Landlock, and live onboarding validation on 26.04 remain pending. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. When Homebrew is available, OpenShell uses its official formula and the gateway appears in `brew services list` as `openshell`; without Homebrew, or when Homebrew refuses to load the official formula from its pinned tap, NemoClaw uses the standalone OpenShell install and detached gateway fallback. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. |
| NVIDIA RTX (consumer and Pro workstation GPUs) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Covers RTX consumer cards and RTX Pro workstation cards on Linux hosts that meet the generic-Linux-GPU requirements (NVIDIA Container Toolkit + CDI present). The provider menu emits managed vLLM behind `NEMOCLAW_EXPERIMENTAL=1` or `NEMOCLAW_PROVIDER=install-vllm` for this host class today; the end-to-end onboard path on this hardware is not yet validated in CI. |
| Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | P1 | No | Requires WSL2 with Docker Desktop backend. |
{/* platform-matrix-full:end */}
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ Follow these steps to reconnect.
```

If Homebrew is present but `openshell` is missing or comes from another tap, rerun the NemoClaw installer.
Onboarding does not use the standalone fallback while Homebrew is present.
If Homebrew refuses to load the formula and cannot confirm its identity, onboarding warns with Homebrew's reason and continues on the standalone fallback.

On Linux package installs, inspect and restart the upstream service.

Expand Down
71 changes: 71 additions & 0 deletions src/lib/onboard/docker-driver-gateway-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,77 @@ describe("docker-driver-gateway-service", () => {
).toThrow("must come from nvidia/openshell");
});

it("continues without the Homebrew service when brew refuses to load the formula (#7707)", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const refusal =
"Error: Refusing to load formula nvidia/openshell/openshell from untrusted tap nvidia/openshell.";
const options = {
commandExists: () => true,
platform: "darwin" as NodeJS.Platform,
spawnSyncImpl: vi.fn((_command: string, args: string[]) =>
args[0] === "info" ? spawnResult(1, refusal) : spawnResult(),
),
};

expect(hasOpenShellGatewayUserService(options)).toBe(false);
expect(hasOpenShellGatewayUserService(options)).toBe(false);
expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(expect.stringContaining(refusal));
});

it.each([
["a generic brew info failure", "Error: Permission denied"],
[
"a refused foreign-tap formula",
"Error: Refusing to load formula other/tap/openshell from untrusted tap other/tap.",
],
])("keeps %s fatal during the formula identity check (#7707)", (_case, reason) => {
expect(() =>
hasOpenShellGatewayUserService({
commandExists: () => true,
platform: "darwin",
spawnSyncImpl: vi.fn((_command: string, args: string[]) =>
args[0] === "info" ? spawnResult(1, reason) : spawnResult(),
),
}),
).toThrow(`OpenShell Homebrew formula identity check failed: ${reason}`);
});

it("still reports a missing formula when brew list is blocked by the untrusted-tap refusal (#7707)", () => {
const refusal =
"Error: Refusing to load formula nvidia/openshell/openshell from untrusted tap nvidia/openshell.";
expect(() =>
hasOpenShellGatewayUserService({
commandExists: () => true,
platform: "darwin",
spawnSyncImpl: () => spawnResult(1, refusal),
}),
).toThrow("official OpenShell Homebrew formula is not installed");
});

it("skips the Homebrew-managed start when the formula identity is unconfirmed (#7707)", async () => {
const refusal =
"Error: Refusing to load formula nvidia/openshell/openshell from untrusted tap nvidia/openshell.";
const started = await startPackageManagedDockerDriverGateway({
clearDockerDriverGatewayRuntimeFiles: () => {},
exitOnFailure: false,
gatewayName: "nemoclaw",
hasOpenShellGatewayUserService: () =>
hasOpenShellGatewayUserService({
commandExists: () => true,
platform: "darwin",
spawnSyncImpl: (_command: string, args: string[]) =>
args[0] === "info" ? spawnResult(1, refusal) : spawnResult(),
}),
registerDockerDriverGatewayEndpoint: () => true,
runCaptureOpenshell: () => "",
skipSandboxBridgeReachability: true,
verifySandboxBridgeGatewayReachableOrExit: async () => {},
});

expect(started).toBe(false);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

it("rejects a missing Homebrew formula when Homebrew is available (#6903)", () => {
expect(() =>
hasOpenShellGatewayUserService({
Expand Down
34 changes: 33 additions & 1 deletion src/lib/onboard/docker-driver-gateway-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ function hasUpstreamOpenShellGatewayUserService(
return getOpenShellGatewayUserServicePaths().some(existsSync);
}

const warnedHomebrewIdentityCheckReasons = new Set<string>();

// Homebrew 6.x refuses to load formulae from taps it has not marked trusted.
// For the pinned official formula that refusal names the right tap, so it is
// not evidence of a wrong formula, and managing the service through brew would
// fail the same way. Continue on the standalone gateway fallback instead of
// aborting; any other brew failure keeps the fail-closed abort (#7707).
// The match is against Homebrew's literal refusal text: if Homebrew rewords
// it, this case reverts to the fail-closed abort, not to a bypass.
function isPinnedTapLoadRefusal(reason: string): boolean {
return reason
.replace(/\s+/g, " ")
.includes(
`Refusing to load formula ${OPENSHELL_GATEWAY_HOMEBREW_TAP}/${OPENSHELL_GATEWAY_HOMEBREW_SERVICE} from untrusted tap ${OPENSHELL_GATEWAY_HOMEBREW_TAP}`,
);
}

function warnHomebrewIdentityCheckUnavailable(reason: string): void {
if (warnedHomebrewIdentityCheckReasons.has(reason)) return;
warnedHomebrewIdentityCheckReasons.add(reason);
console.warn(
` Homebrew could not confirm the OpenShell formula identity; falling back to the standalone gateway.\n ${reason}`,
);
}

function hasOfficialHomebrewFormula(
opts: Pick<
OpenShellGatewayUserServiceOptions,
Expand All @@ -259,7 +284,14 @@ function hasOfficialHomebrewFormula(
env,
spawnSyncImpl,
});
if (!info.ok) throw new Error(`OpenShell Homebrew formula identity check failed: ${info.reason}`);
if (!info.ok) {
const reason = info.reason ?? "brew info failed";
if (isPinnedTapLoadRefusal(reason)) {
warnHomebrewIdentityCheckUnavailable(reason);
return false;
}
throw new Error(`OpenShell Homebrew formula identity check failed: ${reason}`);
}
try {
const parsed = JSON.parse(info.stdout ?? "") as {
formulae?: Array<{ name?: string; tap?: string }>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { createGatewayHostRuntime, type GatewayHostRuntimeDeps } from "./gateway-host-runtime";
import type { PortProbeResult } from "./preflight";

// Homebrew 6.x refuses to load formulae from taps it has not marked trusted,
// so `brew info --json=v2 openshell` fails even though the formula is the
// pinned official one. The refusal reaches the gateway-owner resolution
// through the real spawnSync path, not an injected seam (#7707).
vi.mock("node:child_process", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:child_process")>();
return {
...actual,
spawnSync: vi.fn((command: string, args: readonly string[]) =>
command === "brew" && args[0] === "info"
? {
status: 1,
stderr:
"Error: Refusing to load formula nvidia/openshell/openshell from untrusted tap nvidia/openshell.",
stdout: "",
}
: { status: 0, stderr: "", stdout: "" },
),
};
});

const ORIGINAL_ENV = { ...process.env };

beforeEach(() => {
vi.spyOn(process, "platform", "get").mockReturnValue("darwin");
vi.spyOn(console, "warn").mockImplementation(() => {});
});

afterEach(() => {
process.env = { ...ORIGINAL_ENV };
vi.restoreAllMocks();
});

function createDeps(): GatewayHostRuntimeDeps {
return {
applyOverlayfsAutoFix: () => null,
checkGatewayPortAvailable: async () => ({ ok: true }) as PortProbeResult,
gatewayName: () => "nemoclaw",
gatewayPort: () => 8080,
getGatewayPortListenerRawScan: () => ({ pids: [], complete: true }),
getInstalledOpenshellVersion: () => "0.0.85",
runCaptureOpenshell: () => "healthy",
runOpenshell: () => ({ status: 0 }),
resolveOpenShellGatewayBinary: () => null,
waitForGatewayHttpReady: async () => true,
};
}

describe("gateway host runtime on Homebrew 6.x untrusted tap", () => {
it("resolves a standalone owner instead of aborting when brew refuses the pinned tap (#7707)", () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
const runtime = createGatewayHostRuntime(createDeps());

expect(runtime.getGatewayOwner()).toMatchObject({
gatewayName: "nemoclaw",
gatewayPort: 8080,
mode: "nemoclaw-managed",
source: "standalone",
});
expect(runtime.getGatewayOwner()).toMatchObject({ source: "standalone" });
expect(warn).toHaveBeenCalledTimes(1);
});
});
Loading