diff --git a/docs/get-started/quickstart-hermes.mdx b/docs/get-started/quickstart-hermes.mdx index 82598925a2b..aa46a187bd7 100644 --- a/docs/get-started/quickstart-hermes.mdx +++ b/docs/get-started/quickstart-hermes.mdx @@ -338,6 +338,9 @@ Use these details when your first-run path needs more control. ```bash nemohermes inference set --model --provider ``` + + If the in-sandbox config write or integrity hash update fails, `nemohermes inference set` exits with status `1` after committing the OpenShell route and NemoClaw registry. + Run the printed `nemohermes rebuild` command to make the in-sandbox Hermes configuration match the committed route. diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx index b4380a69f22..c6e64b48422 100644 --- a/docs/reference/commands.mdx +++ b/docs/reference/commands.mdx @@ -4094,8 +4094,23 @@ NemoClaw resolves the OpenShell gateway from the target sandbox's recorded gatew Do not run `openshell inference set` directly on a shared NemoClaw gateway because that bypasses registry compatibility checks and can break other sandboxes. When either flag is missing, `$$nemoclaw inference set` reports both required flags without suggesting a raw OpenShell command. The command updates the host registry immediately after the gateway route changes. + + + + + If the in-sandbox config sync fails, NemoClaw keeps the gateway and registry aligned, warns that the running image may still need a rebuild, and points you to `$$nemoclaw rebuild`. + + + + +If the in-sandbox config write or integrity hash update fails, the OpenShell route and NemoClaw registry remain committed, but the command exits with status `1` and points you to `$$nemoclaw rebuild`. + + + + + Supported provider names are `nvidia-prod`, `nvidia-nim`, `nvidia-router`, `openai-api`, `anthropic-prod`, `compatible-anthropic-endpoint`, `gemini-api`, `compatible-endpoint`, `hermes-provider`, `ollama-local`, and `vllm-local`. Use `--no-verify` only when OpenShell cannot verify the provider at switch time but you have already confirmed the provider and credential. When switching to `compatible-endpoint` or `compatible-anthropic-endpoint` from a different provider family, pass `--endpoint-url` with the trusted custom provider URL and, except for the Hermes case below, `--inference-api` with its API family so NemoClaw can persist a complete route identity for rebuild and shared-gateway checks. diff --git a/src/lib/actions/inference-set-hermes-run.test.ts b/src/lib/actions/inference-set-hermes-run.test.ts index 7edbb03247b..44994cba26d 100644 --- a/src/lib/actions/inference-set-hermes-run.test.ts +++ b/src/lib/actions/inference-set-hermes-run.test.ts @@ -207,7 +207,7 @@ describe("runInferenceSet Hermes routing", () => { expect(seedOrder).toBeGreaterThan(writeOrder); }); - it("does not re-seed the dashboard when the in-sandbox config write fails (#6893)", async () => { + it("fails after commit when the in-sandbox config write fails (#7083)", async () => { const config: ConfigObject = { model: { default: "moonshotai/kimi-k2.6", provider: "custom" }, }; @@ -227,22 +227,36 @@ describe("runInferenceSet Hermes routing", () => { throw new Error("write failed"); }); - await runInferenceSet( - { + await expect( + runInferenceSet( + { + provider: "hermes-provider", + model: "openai/gpt-5.4-mini", + sandboxName: "hermes", + noVerify: true, + }, + deps, + ), + ).rejects.toMatchObject({ + name: "InferenceSetError", + exitCode: 1, + message: expect.stringMatching(/Hermes inference route synchronization did not complete/), + }); + + // A failed in-sandbox Hermes config write leaves the old config in place; re-seeding the + // dashboard from it would be pointless. The host route remains committed, but + // the command must fail so automation cannot accept a partial switch. + expect(deps.calls.updateSandbox).toHaveBeenCalledWith( + "hermes", + expect.objectContaining({ provider: "hermes-provider", model: "openai/gpt-5.4-mini", - sandboxName: "hermes", - noVerify: true, - }, - deps, + }), ); - - // A failed gateway-config write leaves the old config in place; re-seeding the - // dashboard from it would be pointless (and the guidance is to rebuild). expect(deps.calls.seedHermesDashboardConfig).not.toHaveBeenCalled(); }); - it("does not re-seed or report synced when the config hash refresh fails (#6893)", async () => { + it("fails after commit when the config hash refresh fails (#7083)", async () => { const config: ConfigObject = { model: { default: "moonshotai/kimi-k2.6", provider: "custom" }, }; @@ -262,17 +276,30 @@ describe("runInferenceSet Hermes routing", () => { throw new Error("hash refresh failed"); }); - await runInferenceSet( - { + await expect( + runInferenceSet( + { + provider: "hermes-provider", + model: "openai/gpt-5.4-mini", + sandboxName: "hermes", + noVerify: true, + }, + deps, + ), + ).rejects.toMatchObject({ + name: "InferenceSetError", + exitCode: 1, + message: expect.stringMatching(/Hermes inference route synchronization did not complete/), + }); + + expect(deps.calls.writeSandboxConfig).toHaveBeenCalledOnce(); + expect(deps.calls.updateSandbox).toHaveBeenCalledWith( + "hermes", + expect.objectContaining({ provider: "hermes-provider", model: "openai/gpt-5.4-mini", - sandboxName: "hermes", - noVerify: true, - }, - deps, + }), ); - - expect(deps.calls.writeSandboxConfig).toHaveBeenCalledOnce(); expect(deps.calls.seedHermesDashboardConfig).not.toHaveBeenCalled(); const logs = deps.calls.log.mock.calls.map((call) => String(call[0])); expect(logs.some((line) => line.includes("failed to refresh its integrity hash"))).toBe(true); diff --git a/src/lib/actions/inference-set.ts b/src/lib/actions/inference-set.ts index d0e3482524c..a69a682e3eb 100644 --- a/src/lib/actions/inference-set.ts +++ b/src/lib/actions/inference-set.ts @@ -1239,8 +1239,9 @@ async function runInferenceSetWithoutHostLock( ? ` Syncing Hermes model route in sandbox '${sandboxName}'...` : ` Syncing OpenClaw model identity in sandbox '${sandboxName}'...`, ); - // In-sandbox config is the last, crash-prone layer (gateway + registry already consistent): - // - don't abort on failure; track whether it synced, never report a false "synced" + // In-sandbox config is the last, crash-prone layer (gateway + registry already consistent). + // OpenClaw keeps its existing degraded result on failure. Hermes finalizes the committed + // route and registry, then returns an error so automation cannot accept partial convergence. // Two degraded states, both fixed by `rebuild` (regenerates openclaw.json + .config-hash from registry): // - write fails: config left old (old .config-hash still matches it) // - hash recompute fails: config new but .config-hash stale -> integrity-guard mismatch @@ -1299,7 +1300,7 @@ async function runInferenceSetWithoutHostLock( reasoningEffortRequest, ); - return finalizeInferenceMutation( + const mutation = finalizeInferenceMutation( { agentName, configChanged: patched.changed, @@ -1319,6 +1320,14 @@ async function runInferenceSetWithoutHostLock( }, deps, ); + if (agentName === "hermes" && !inSandboxConfigSynced) { + throw new InferenceSetError( + `Hermes inference route synchronization did not complete for '${sandboxName}'. ` + + `The OpenShell route and NemoClaw registry remain committed, but the in-sandbox ` + + `Hermes configuration did not fully converge. Run '${CLI_NAME} ${sandboxName} rebuild' to converge it.`, + ); + } + return mutation; } catch (error) { if (!providerMutation) throw error; if (restoredSelectionAfterProviderFailure) throw error;