From 314334b68c4b8343892213852cebd9f60053270f Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Fri, 12 Jun 2026 03:06:29 +0800 Subject: [PATCH 1/7] fix(cli): guide Hermes users to dashboard auth on gateway-token For Hermes sandboxes, 'nemoclaw gateway-token' dead-ended on the OpenClaw-only 'not applicable' line, leaving users following generic dashboard-token quickstart patterns without a next step during Day0 verification. Surface a Hermes-specific hint that points at the supported dashboard auth path (dashboard-url) and notes that Hermes dashboard auth is read from the in-sandbox config (~/.hermes/config.yaml), not a gateway token. Other non-OpenClaw agents keep the single explanatory line. Closes #5249 Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/gateway-token-command.test.ts | 34 ++++++++++++++++++++++++--- src/lib/gateway-token-command.ts | 25 +++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/lib/gateway-token-command.test.ts b/src/lib/gateway-token-command.test.ts index 53a539fa2f..a902a25ce1 100644 --- a/src/lib/gateway-token-command.test.ts +++ b/src/lib/gateway-token-command.test.ts @@ -156,15 +156,43 @@ describe("runGatewayTokenCommand", () => { expect(getSandboxAgent).toHaveBeenCalledWith("hermes"); expect(fetchToken).not.toHaveBeenCalled(); expect(sinks.out).toEqual([]); - // Issue #3180 contract: a single agent-aware "not applicable" line. + // Nothing is written to the live stderr sink; diagnostics travel on the + // thrown error's `lines` so the caller renders them. expect(sinks.err).toEqual([]); - expect(thrown?.lines).toHaveLength(1); - const stderr = thrown?.lines[0] ?? ""; + const stderr = thrown?.lines.join("\n") ?? ""; + // Issue #3180 contract: an agent-aware "not applicable" lead line. expect(stderr).toMatch(/hermes/); expect(stderr).toMatch(/OpenClaw/); expect(stderr).toMatch(/not applicable/i); expect(stderr).not.toMatch(/sandbox is running/i); expect(stderr).not.toMatch(/ExitError|@oclif\/core|at Object\.exit/); + // Issue #5249: the Hermes message must direct users to the supported + // dashboard auth path instead of dead-ending on the OpenClaw-only note. + expect(stderr).toMatch(/dashboard-url/); + expect(stderr).toMatch(/\.hermes\/config\.yaml/); + }); + + it("keeps a single explanatory line for non-Hermes, non-OpenClaw agents", () => { + const sinks = makeSinks(); + let thrown: GatewayTokenCommandError | null = null; + try { + runGatewayTokenCommand( + "beta", + { quiet: false }, + { + fetchToken: () => "unused", + getSandboxAgent: () => "someother", + log: sinks.log, + error: sinks.error, + }, + ); + } catch (error) { + thrown = error as GatewayTokenCommandError; + } + expect(thrown).toBeInstanceOf(GatewayTokenCommandError); + expect(thrown?.lines).toHaveLength(1); + expect(thrown?.lines[0]).toMatch(/not applicable/i); + expect(thrown?.lines[0]).not.toMatch(/dashboard-url/); }); it("falls back to fetchToken when the agent lookup throws", () => { diff --git a/src/lib/gateway-token-command.ts b/src/lib/gateway-token-command.ts index 4b40291554..8c930a451c 100644 --- a/src/lib/gateway-token-command.ts +++ b/src/lib/gateway-token-command.ts @@ -54,6 +54,27 @@ function gatewayTokenFail(lines: string | readonly string[], exitCode = 1): neve const SECURITY_WARNING = "Treat this token like a password -- do not log, share, or commit it."; +/** + * Build the agent-aware "not applicable" diagnostic for a non-OpenClaw agent. + * + * NCQ #5249: the bare "this command only supports OpenClaw" line (NCQ #3180) + * leaves Hermes users following generic dashboard-token quickstart patterns + * without a next step. For Hermes specifically, point them at the supported + * dashboard auth path so Day0 verification is not a dead end. Other + * non-OpenClaw agents keep the single explanatory line. + */ +function notApplicableLines(sandboxName: string, agent: string): readonly string[] { + const lead = ` gateway-token is not applicable for sandbox '${sandboxName}': it uses the '${agent}' agent, which does not expose a gateway auth token. This command only supports the OpenClaw agent.`; + if (agent === "hermes") { + return [ + lead, + ` For Hermes dashboard access, run: nemoclaw ${sandboxName} dashboard-url`, + " Hermes dashboard auth is read from the in-sandbox config (~/.hermes/config.yaml), not a gateway token.", + ]; + } + return [lead]; +} + /** * Run the gateway-token command. Throws {@link GatewayTokenCommandError} on * failure. The caller is responsible for rendering failures and for having @@ -80,9 +101,7 @@ export function runGatewayTokenCommand( } } if (resolvedAgent && resolvedAgent !== "openclaw") { - gatewayTokenFail( - ` gateway-token is not applicable for sandbox '${sandboxName}': it uses the '${resolvedAgent}' agent, which does not expose a gateway auth token. This command only supports the OpenClaw agent.`, - ); + gatewayTokenFail(notApplicableLines(sandboxName, resolvedAgent)); } let token: string | null; From 11ec7f5560ecf47e0f495662c9e2a526daefe34d Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Fri, 12 Jun 2026 10:27:03 +0800 Subject: [PATCH 2/7] docs(cli): point Hermes gateway-token note to dashboard-url The gateway-token CLI hint now directs Hermes users to dashboard-url for dashboard access (#5249). Align the reference docs so the Hermes gateway-token note mirrors that guidance instead of only pointing at the OpenAI-compatible API URL. Signed-off-by: Jason Ma Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/reference/commands-nemohermes.mdx | 1 + docs/reference/commands.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/reference/commands-nemohermes.mdx b/docs/reference/commands-nemohermes.mdx index fa37231907..cd639ab1fc 100644 --- a/docs/reference/commands-nemohermes.mdx +++ b/docs/reference/commands-nemohermes.mdx @@ -622,6 +622,7 @@ Use `nemohermes my-assistant status` to see both the dashboard and API endpoints `gateway-token` is not applicable to Hermes sandboxes. Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token. +For browser access to the dashboard, use `nemohermes dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports. ### `nemohermes destroy` diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx index 70d7be83e6..11b9a1aa21 100644 --- a/docs/reference/commands.mdx +++ b/docs/reference/commands.mdx @@ -835,6 +835,7 @@ Do not log it, share it, or commit it to version control. `gateway-token` is not applicable to Hermes sandboxes. Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token. +For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports. From 8d336ee93e30167ccc1646105cece5d84585676e Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Mon, 22 Jun 2026 10:00:28 -0700 Subject: [PATCH 3/7] docs(commands): resync agent variant after #5252 source edit The PR's manual edit to docs/reference/commands.mdx:909 used the canonical `my-assistant` placeholder, but the matching line in the generated docs/reference/commands-nemohermes.mdx still carried `` from an earlier draft, so the docs preview job kept failing the agent-variant sync check. Run `npm run docs:sync-agent-variants` to re-derive the generated file from source so the two stay aligned. Signed-off-by: Charan Jagwani --- docs/reference/commands-nemohermes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/commands-nemohermes.mdx b/docs/reference/commands-nemohermes.mdx index bd4cca2780..f82cc7a1e0 100644 --- a/docs/reference/commands-nemohermes.mdx +++ b/docs/reference/commands-nemohermes.mdx @@ -658,7 +658,7 @@ Use `nemohermes my-assistant status` to see both the dashboard and API endpoints `gateway-token` is not applicable to Hermes sandboxes. Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token. -For browser access to the dashboard, use `nemohermes dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. +For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports. ### `nemohermes destroy` From 47c531779525fe651375d6e3dd1f19983558959a Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Mon, 22 Jun 2026 11:35:27 -0700 Subject: [PATCH 4/7] fix(gateway-token): pull CLI name from branding for Hermes hint PR Review Advisor PRA-2 on #5252: the Hermes diagnostic at notApplicableLines() hardcoded "nemoclaw" in the next-step hint, so a user who launched through the nemohermes alias still got "nemoclaw dashboard-url" as the suggestion. The docs change in this PR already says "nemohermes dashboard-url", and src/lib/cli/branding.ts already exposes the invoked CLI name via NEMOCLAW_INVOKED_AS for exactly this reason. Switch the helper to read getAgentBranding().cli at call time so the hint matches whatever binary the user actually typed. Resolving at call time (not module load) keeps the hint in sync with NEMOCLAW_INVOKED_AS even when the env var is set after import. Tests pin both alias paths: nemohermes invocation renders the nemohermes hint, and nemoclaw invocation renders the nemoclaw hint. Signed-off-by: Charan Jagwani --- src/lib/gateway-token-command.test.ts | 71 +++++++++++++++++++++++++++ src/lib/gateway-token-command.ts | 10 +++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/lib/gateway-token-command.test.ts b/src/lib/gateway-token-command.test.ts index a902a25ce1..85ba956d85 100644 --- a/src/lib/gateway-token-command.test.ts +++ b/src/lib/gateway-token-command.test.ts @@ -172,6 +172,77 @@ describe("runGatewayTokenCommand", () => { expect(stderr).toMatch(/\.hermes\/config\.yaml/); }); + // PRA-2 on #5252: the Hermes diagnostic must reflect the invoked CLI alias + // so users who type `nemohermes` see `nemohermes` in the next-step hint, + // not the hardcoded `nemoclaw`. The launcher binaries set + // `NEMOCLAW_INVOKED_AS` so `getAgentBranding().cli` resolves to the right + // command at runtime. + it("Hermes diagnostic uses nemohermes when invoked through the NemoHermes alias", () => { + const previousInvokedAs = process.env.NEMOCLAW_INVOKED_AS; + process.env.NEMOCLAW_INVOKED_AS = "nemohermes"; + try { + const sinks = makeSinks(); + let thrown: GatewayTokenCommandError | null = null; + try { + runGatewayTokenCommand( + "hermes", + { quiet: false }, + { + fetchToken: () => "should-not-be-called", + getSandboxAgent: () => "hermes", + log: sinks.log, + error: sinks.error, + }, + ); + } catch (error) { + thrown = error as GatewayTokenCommandError; + } + expect(thrown).toBeInstanceOf(GatewayTokenCommandError); + const stderr = thrown?.lines.join("\n") ?? ""; + expect(stderr).toContain("For Hermes dashboard access, run: nemohermes hermes dashboard-url"); + expect(stderr).not.toContain("nemoclaw hermes dashboard-url"); + } finally { + if (previousInvokedAs === undefined) { + delete process.env.NEMOCLAW_INVOKED_AS; + } else { + process.env.NEMOCLAW_INVOKED_AS = previousInvokedAs; + } + } + }); + + it("Hermes diagnostic uses nemoclaw when Hermes is selected through the nemoclaw binary", () => { + const previousInvokedAs = process.env.NEMOCLAW_INVOKED_AS; + process.env.NEMOCLAW_INVOKED_AS = "nemoclaw"; + try { + const sinks = makeSinks(); + let thrown: GatewayTokenCommandError | null = null; + try { + runGatewayTokenCommand( + "hermes", + { quiet: false }, + { + fetchToken: () => "should-not-be-called", + getSandboxAgent: () => "hermes", + log: sinks.log, + error: sinks.error, + }, + ); + } catch (error) { + thrown = error as GatewayTokenCommandError; + } + expect(thrown).toBeInstanceOf(GatewayTokenCommandError); + const stderr = thrown?.lines.join("\n") ?? ""; + expect(stderr).toContain("For Hermes dashboard access, run: nemoclaw hermes dashboard-url"); + expect(stderr).not.toContain("nemohermes hermes dashboard-url"); + } finally { + if (previousInvokedAs === undefined) { + delete process.env.NEMOCLAW_INVOKED_AS; + } else { + process.env.NEMOCLAW_INVOKED_AS = previousInvokedAs; + } + } + }); + it("keeps a single explanatory line for non-Hermes, non-OpenClaw agents", () => { const sinks = makeSinks(); let thrown: GatewayTokenCommandError | null = null; diff --git a/src/lib/gateway-token-command.ts b/src/lib/gateway-token-command.ts index 8c930a451c..d272d8b16d 100644 --- a/src/lib/gateway-token-command.ts +++ b/src/lib/gateway-token-command.ts @@ -12,6 +12,8 @@ * exit 1: token unavailable; diagnostics written to stderr. */ +import { getAgentBranding } from "./cli/branding"; + export interface GatewayTokenCommandDeps { /** Pull gateway.auth.token from the sandbox config (host-side helper). */ fetchToken: (sandboxName: string) => string | null; @@ -66,9 +68,15 @@ const SECURITY_WARNING = "Treat this token like a password -- do not log, share, function notApplicableLines(sandboxName: string, agent: string): readonly string[] { const lead = ` gateway-token is not applicable for sandbox '${sandboxName}': it uses the '${agent}' agent, which does not expose a gateway auth token. This command only supports the OpenClaw agent.`; if (agent === "hermes") { + // Pull the invoked CLI name from branding so the hint matches whatever the + // user actually typed: `nemohermes` when launched through the alias, + // `nemoclaw` when Hermes is selected through the default binary. Resolving + // at call time (not import time) keeps the hint in sync with + // NEMOCLAW_INVOKED_AS even when the env var is set after module load. + const cliName = getAgentBranding().cli; return [ lead, - ` For Hermes dashboard access, run: nemoclaw ${sandboxName} dashboard-url`, + ` For Hermes dashboard access, run: ${cliName} ${sandboxName} dashboard-url`, " Hermes dashboard auth is read from the in-sandbox config (~/.hermes/config.yaml), not a gateway token.", ]; } From 47791ebd5e0c3d1832122a3be9aa97a84b6b9fb4 Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Mon, 22 Jun 2026 11:40:26 -0700 Subject: [PATCH 5/7] test(gateway-token): assert multi-line Hermes diagnostic flows through oclif wrapper PR Review Advisor PRA-T3 on #5252 asked for end-to-end coverage of the multi-line Hermes diagnostic at the oclif wrapper layer. The existing per-helper test pins the line content and ordering; the existing wrapper test pins the exit-code contract for a single-line error. Neither covered the case the advisor flagged: multi-line error survives intact through GatewayTokenCliCommand.run() with every line written to console.error and no leaked oclif stack trace. Add a focused test that mocks runGatewayTokenCommand to throw the three-line Hermes diagnostic, spies on console.error, and asserts each line was written, process.exitCode is 1, and the captured output never matches an ExitError / @oclif/core / at Object.exit trace. Signed-off-by: Charan Jagwani --- .../simple-global-oclif-adapters.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/commands/simple-global-oclif-adapters.test.ts b/src/commands/simple-global-oclif-adapters.test.ts index e6aab52b5d..e50d132702 100644 --- a/src/commands/simple-global-oclif-adapters.test.ts +++ b/src/commands/simple-global-oclif-adapters.test.ts @@ -202,6 +202,41 @@ describe("simple global oclif adapters", () => { } }); + it("renders every line of a multi-line Hermes diagnostic to stderr without leaking an oclif stack trace", async () => { + // PRA-T3 on #5252: when the helper throws a multi-line + // GatewayTokenCommandError for a Hermes sandbox, the wrapper must + // (a) write every line via console.error, (b) signal failure via + // process.exitCode, and (c) leak no @oclif/core ExitError stack trace. + const hermesLines = [ + " gateway-token is not applicable for sandbox 'hermes': it uses the 'hermes' agent, which does not expose a gateway auth token. This command only supports the OpenClaw agent.", + " For Hermes dashboard access, run: nemohermes hermes dashboard-url", + " Hermes dashboard auth is read from the in-sandbox config (~/.hermes/config.yaml), not a gateway token.", + ]; + mocks.runGatewayTokenCommand.mockImplementationOnce(() => { + throw new mocks.GatewayTokenCommandError(hermesLines, 1); + }); + setGatewayTokenRuntimeBridgeFactoryForTest(() => ({ + fetchGatewayAuthTokenFromSandbox: mocks.fetchGatewayAuthTokenFromSandbox, + getSandboxAgent: () => "hermes", + })); + + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); + const previousExitCode = process.exitCode; + process.exitCode = undefined; + try { + await expect(GatewayTokenCliCommand.run(["hermes"], rootDir)).resolves.toBeUndefined(); + expect(process.exitCode).toBe(1); + for (const line of hermesLines) { + expect(errorSpy).toHaveBeenCalledWith(line); + } + const combined = errorSpy.mock.calls.map((args) => args.join(" ")).join("\n"); + expect(combined).not.toMatch(/ExitError|@oclif\/core|at Object\.exit/); + } finally { + process.exitCode = previousExitCode; + errorSpy.mockRestore(); + } + }); + it("clears a stale non-zero process.exitCode on a successful gateway-token run", async () => { // CodeRabbit #3182: if a prior run() left process.exitCode = 1, a later // successful invocation must still report success. Always overwrite. From f2dd7efc1531099847c9b917c049e36f298a431d Mon Sep 17 00:00:00 2001 From: Charan Jagwani Date: Mon, 22 Jun 2026 11:45:34 -0700 Subject: [PATCH 6/7] test(gateway-token): replace if/else env restore with vi.stubEnv The codebase-growth-guardrails CI check fails on PR test files that add conditional statements (NemoClaw asserts deterministic tests). The two new PRA-2 tests added in 47c531779 used a try/finally with `if (previousInvokedAs === undefined) delete ... else assign ...` to restore process.env.NEMOCLAW_INVOKED_AS, which tripped the guard ("up from 0 if statements"). Switch to vitest's built-in env stubbing: vi.stubEnv() sets the var for the test and vi.unstubAllEnvs() restores all stubbed vars in afterEach. No conditionals, deterministic restoration, and shorter test bodies. Signed-off-by: Charan Jagwani --- src/lib/gateway-token-command.test.ts | 103 +++++++++++--------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/src/lib/gateway-token-command.test.ts b/src/lib/gateway-token-command.test.ts index 85ba956d85..d83b6809fb 100644 --- a/src/lib/gateway-token-command.test.ts +++ b/src/lib/gateway-token-command.test.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { GatewayTokenCommandError, @@ -176,71 +176,58 @@ describe("runGatewayTokenCommand", () => { // so users who type `nemohermes` see `nemohermes` in the next-step hint, // not the hardcoded `nemoclaw`. The launcher binaries set // `NEMOCLAW_INVOKED_AS` so `getAgentBranding().cli` resolves to the right - // command at runtime. + // command at runtime. `vi.stubEnv` + `vi.unstubAllEnvs` keep the test + // deterministic without conditional state restoration in a `finally`. + afterEach(() => { + vi.unstubAllEnvs(); + }); + it("Hermes diagnostic uses nemohermes when invoked through the NemoHermes alias", () => { - const previousInvokedAs = process.env.NEMOCLAW_INVOKED_AS; - process.env.NEMOCLAW_INVOKED_AS = "nemohermes"; + vi.stubEnv("NEMOCLAW_INVOKED_AS", "nemohermes"); + const sinks = makeSinks(); + let thrown: GatewayTokenCommandError | null = null; try { - const sinks = makeSinks(); - let thrown: GatewayTokenCommandError | null = null; - try { - runGatewayTokenCommand( - "hermes", - { quiet: false }, - { - fetchToken: () => "should-not-be-called", - getSandboxAgent: () => "hermes", - log: sinks.log, - error: sinks.error, - }, - ); - } catch (error) { - thrown = error as GatewayTokenCommandError; - } - expect(thrown).toBeInstanceOf(GatewayTokenCommandError); - const stderr = thrown?.lines.join("\n") ?? ""; - expect(stderr).toContain("For Hermes dashboard access, run: nemohermes hermes dashboard-url"); - expect(stderr).not.toContain("nemoclaw hermes dashboard-url"); - } finally { - if (previousInvokedAs === undefined) { - delete process.env.NEMOCLAW_INVOKED_AS; - } else { - process.env.NEMOCLAW_INVOKED_AS = previousInvokedAs; - } + runGatewayTokenCommand( + "hermes", + { quiet: false }, + { + fetchToken: () => "should-not-be-called", + getSandboxAgent: () => "hermes", + log: sinks.log, + error: sinks.error, + }, + ); + } catch (error) { + thrown = error as GatewayTokenCommandError; } + expect(thrown).toBeInstanceOf(GatewayTokenCommandError); + const stderr = thrown?.lines.join("\n") ?? ""; + expect(stderr).toContain("For Hermes dashboard access, run: nemohermes hermes dashboard-url"); + expect(stderr).not.toContain("nemoclaw hermes dashboard-url"); }); it("Hermes diagnostic uses nemoclaw when Hermes is selected through the nemoclaw binary", () => { - const previousInvokedAs = process.env.NEMOCLAW_INVOKED_AS; - process.env.NEMOCLAW_INVOKED_AS = "nemoclaw"; + vi.stubEnv("NEMOCLAW_INVOKED_AS", "nemoclaw"); + const sinks = makeSinks(); + let thrown: GatewayTokenCommandError | null = null; try { - const sinks = makeSinks(); - let thrown: GatewayTokenCommandError | null = null; - try { - runGatewayTokenCommand( - "hermes", - { quiet: false }, - { - fetchToken: () => "should-not-be-called", - getSandboxAgent: () => "hermes", - log: sinks.log, - error: sinks.error, - }, - ); - } catch (error) { - thrown = error as GatewayTokenCommandError; - } - expect(thrown).toBeInstanceOf(GatewayTokenCommandError); - const stderr = thrown?.lines.join("\n") ?? ""; - expect(stderr).toContain("For Hermes dashboard access, run: nemoclaw hermes dashboard-url"); - expect(stderr).not.toContain("nemohermes hermes dashboard-url"); - } finally { - if (previousInvokedAs === undefined) { - delete process.env.NEMOCLAW_INVOKED_AS; - } else { - process.env.NEMOCLAW_INVOKED_AS = previousInvokedAs; - } + runGatewayTokenCommand( + "hermes", + { quiet: false }, + { + fetchToken: () => "should-not-be-called", + getSandboxAgent: () => "hermes", + log: sinks.log, + error: sinks.error, + }, + ); + } catch (error) { + thrown = error as GatewayTokenCommandError; } + expect(thrown).toBeInstanceOf(GatewayTokenCommandError); + const stderr = thrown?.lines.join("\n") ?? ""; + expect(stderr).toContain("For Hermes dashboard access, run: nemoclaw hermes dashboard-url"); + expect(stderr).not.toContain("nemohermes hermes dashboard-url"); }); it("keeps a single explanatory line for non-Hermes, non-OpenClaw agents", () => { From 4b9be14e1e0903afec999a3fbb397f86337137ce Mon Sep 17 00:00:00 2001 From: Jason Ma Date: Tue, 23 Jun 2026 14:39:34 +0800 Subject: [PATCH 7/7] fix: use resolved /sandbox/.hermes/config.yaml path in Hermes hint The gateway-token 'not applicable' diagnostic and the commands reference docs cited ~/.hermes/config.yaml -- the internal manifest target form -- rather than the resolved in-sandbox path /sandbox/.hermes/config.yaml that users actually see and that the rest of the codebase/docs use. Aligns the user-facing message and docs with the canonical path (addresses CodeRabbit review on #5252). Signed-off-by: Jason Ma Co-Authored-By: Claude Opus 4.8 --- docs/reference/commands-nemohermes.mdx | 2 +- docs/reference/commands.mdx | 2 +- src/lib/gateway-token-command.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/commands-nemohermes.mdx b/docs/reference/commands-nemohermes.mdx index 800a15e811..04a0e5c796 100644 --- a/docs/reference/commands-nemohermes.mdx +++ b/docs/reference/commands-nemohermes.mdx @@ -658,7 +658,7 @@ Use `nemohermes my-assistant status` to see both the dashboard and API endpoints `gateway-token` is not applicable to Hermes sandboxes. Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token. -For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. +For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`/sandbox/.hermes/config.yaml`), not a gateway token. If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports. ### `nemohermes destroy` diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx index d6fc663f67..8114ab1342 100644 --- a/docs/reference/commands.mdx +++ b/docs/reference/commands.mdx @@ -906,7 +906,7 @@ Do not log it, share it, or commit it to version control. `gateway-token` is not applicable to Hermes sandboxes. Hermes API access uses bearer-token authentication configured through the Hermes runtime, not the OpenClaw gateway token. -For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`~/.hermes/config.yaml`), not a gateway token. +For browser access to the dashboard, use `nemohermes my-assistant dashboard-url`; Hermes dashboard auth is read from the in-sandbox config (`/sandbox/.hermes/config.yaml`), not a gateway token. If you need the endpoint for an OpenAI-compatible client, use `nemohermes my-assistant status` and the API URL it reports. diff --git a/src/lib/gateway-token-command.ts b/src/lib/gateway-token-command.ts index d272d8b16d..cddfd39789 100644 --- a/src/lib/gateway-token-command.ts +++ b/src/lib/gateway-token-command.ts @@ -77,7 +77,7 @@ function notApplicableLines(sandboxName: string, agent: string): readonly string return [ lead, ` For Hermes dashboard access, run: ${cliName} ${sandboxName} dashboard-url`, - " Hermes dashboard auth is read from the in-sandbox config (~/.hermes/config.yaml), not a gateway token.", + " Hermes dashboard auth is read from the in-sandbox config (/sandbox/.hermes/config.yaml), not a gateway token.", ]; } return [lead];