From 0260b40e547d8a34256a4f553e51442347acbd18 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Fri, 7 Aug 2026 02:43:46 -0700 Subject: [PATCH 1/2] chore(e2e): claim Hermes dashboard permission failure Signed-off-by: Prekshi Vyas From f44514b815e157cdec939aed359acc479fdd0825 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Fri, 7 Aug 2026 02:46:16 -0700 Subject: [PATCH 2/2] fix(hermes): restore dashboard runtime permissions Signed-off-by: Prekshi Vyas --- agents/hermes/start.sh | 10 ++--- ...ermes-dashboard-permission-restore.test.ts | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 test/hermes-dashboard-permission-restore.test.ts diff --git a/agents/hermes/start.sh b/agents/hermes/start.sh index 27cd7245024..e62ce782e65 100755 --- a/agents/hermes/start.sh +++ b/agents/hermes/start.sh @@ -1608,13 +1608,13 @@ wait_for_hermes_gateway_internal() { restore_hermes_config_permissions_after_dashboard_start() { [ "$(id -u)" -eq 0 ] || return 0 - # Hermes dashboard startup may tighten HERMES_HOME to 0700 because it runs as - # the sandbox owner. The gateway process runs as the separate gateway user and - # reads config via sandbox-group membership, so restore NemoClaw's shared - # mutable-root mode after the dashboard has performed its startup checks. + # Hermes dashboard startup may tighten HERMES_HOME and its runtime directories + # to 0700 because it runs as the sandbox owner. The gateway process runs as the + # separate gateway user and relies on sandbox-group membership, so restore the + # complete bounded writable layout after the dashboard startup checks. local attempts=0 while [ "$attempts" -lt 5 ]; do - ensure_hermes_config_root_mode || return 1 + repair_hermes_startup_layout || return 1 attempts=$((attempts + 1)) sleep 1 done diff --git a/test/hermes-dashboard-permission-restore.test.ts b/test/hermes-dashboard-permission-restore.test.ts new file mode 100644 index 00000000000..b989782c160 --- /dev/null +++ b/test/hermes-dashboard-permission-restore.test.ts @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +import { extractShellFunction } from "./support/hermes-shell-harness"; + +const START_SCRIPT = path.join(import.meta.dirname, "..", "agents", "hermes", "start.sh"); + +describe("Hermes post-Dashboard permission restore", () => { + it("reasserts the complete writable layout throughout the bounded startup window", () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-hermes-dashboard-repair-")); + const scriptPath = path.join(tmpDir, "run.sh"); + const src = fs.readFileSync(START_SCRIPT, "utf-8"); + fs.writeFileSync( + scriptPath, + [ + "#!/usr/bin/env bash", + "set -euo pipefail", + extractShellFunction(src, "restore_hermes_config_permissions_after_dashboard_start"), + 'id() { [ "${1:-}" = "-u" ] && printf "0\\n"; }', + 'repair_hermes_startup_layout() { printf "repair-layout\\n"; }', + "sleep() { :; }", + "restore_hermes_config_permissions_after_dashboard_start", + ].join("\n"), + { mode: 0o700 }, + ); + + try { + const result = spawnSync("bash", [scriptPath], { + encoding: "utf-8", + timeout: 5000, + env: process.env, + }); + expect(result.status).toBe(0); + expect(result.stdout.trim().split("\n")).toEqual(Array(5).fill("repair-layout")); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }); +});