diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index ee4320830d3..2d304655ee3 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -123,6 +123,7 @@ COPY scripts/gateway-control.sh /usr/local/bin/nemoclaw-gateway-control COPY scripts/managed-gateway-control.py /usr/local/lib/nemoclaw/managed-gateway-control.py COPY agents/hermes/validate-env-secret-boundary.py /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py COPY agents/hermes/patch-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py +COPY agents/hermes/patch-hermes-sqlite-temp-store.py /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py COPY agents/hermes/patch-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py COPY agents/hermes/patch-profile-policy-defaults.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py COPY agents/hermes/managed_policy.py /usr/local/lib/nemoclaw/managed_policy.py @@ -326,7 +327,7 @@ RUN find /opt/nemoclaw-hermes-config -type d -exec chmod 755 {} + \ /scripts/patch-bundled-npm-tar.mts \ && chmod -R a+rX /src/lib/messaging -ARG NEMOCLAW_HERMES_IMAGE_BUILD_PROBES_SHA256=61fec509d9c0229650f71d1fc53d103a39fd830976213dbdef0d222592981d03 +ARG NEMOCLAW_HERMES_IMAGE_BUILD_PROBES_SHA256=89f530da6a8296c8bab449a2a324d8efedca1a90b04a5e0f57cf9203ed2011cd # hadolint ignore=DL4006 RUN printf '%s %s\n' \ "$NEMOCLAW_HERMES_IMAGE_BUILD_PROBES_SHA256" /opt/nemoclaw-hermes-config/image-build-probes.py \ @@ -361,7 +362,7 @@ RUN chmod -R a+rX /opt/nemoclaw-blueprint/ # profile hook, bashrc hook, or root-owned helper mode. Remove it once the # minimum supported Hermes sandbox base tag guarantees those artifacts and # test/sandbox-rlimit-hooks.test.ts covers that base. -RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-managed-startup-hold /usr/local/bin/nemoclaw-managed-bootstrap /usr/local/lib/nemoclaw/sandbox-init.sh /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py /usr/local/lib/nemoclaw/seed-hermes-dashboard-config.py /usr/local/lib/nemoclaw/hermes-runtime-config-guard.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py \ +RUN chmod 755 /usr/local/bin/nemoclaw-start /usr/local/bin/nemoclaw-managed-startup-hold /usr/local/bin/nemoclaw-managed-bootstrap /usr/local/lib/nemoclaw/sandbox-init.sh /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py /usr/local/lib/nemoclaw/seed-hermes-dashboard-config.py /usr/local/lib/nemoclaw/hermes-runtime-config-guard.py /usr/local/lib/nemoclaw/finalize-tirith-marker.py /usr/local/lib/nemoclaw/hermes-mcp-config-transaction.py \ && chown root:root /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/gateway-supervisor.sh /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/share/nemoclaw/state-lock-plan.json /usr/local/lib/nemoclaw/managed-gateway-control.py /usr/local/lib/nemoclaw/build-hermes-mcp-digest.py /usr/local/lib/nemoclaw/hermes-cron-restore-control.py /usr/local/lib/nemoclaw/openshell-child-visible-credentials.v0.0.85.json \ && chmod 700 /usr/local/bin/nemoclaw-gateway-control /usr/local/lib/nemoclaw/hermes-cron-restore-control.py \ && chmod 500 /usr/local/lib/nemoclaw/state-dir-guard.py /usr/local/lib/nemoclaw/managed-gateway-control.py \ @@ -500,6 +501,27 @@ RUN /usr/bin/python3 -I /usr/local/lib/nemoclaw/patch-hermes-session-list-previe && HERMES_HOME="$(mktemp -d)" /opt/hermes/.venv/bin/python -I \ /opt/nemoclaw-hermes-config/image-build-probes.py session-preview +# Hermes v0.19.0 SessionDB does not set PRAGMA temp_store=MEMORY, so SQLite +# falls back to file-based temp storage for FK constraint processing (for +# example, the ON DELETE CASCADE on session_model_usage -> sessions). The +# OpenShell sandbox execution context used by +# `nemohermes sessions delete ` restricts the temp-file creation +# syscalls that SQLite's FK engine relies on, causing every +# `DELETE FROM sessions` with FK enforcement to raise +# `sqlite3.OperationalError: unable to open database file` (#8301). Inserting +# PRAGMA temp_store=MEMORY before PRAGMA foreign_keys=ON ensures FK processing +# uses in-memory storage. Docker execution permits the file-based storage path. +ARG NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256=2db46b6cd358f4d181999bbddbc0d35d035a67ee1092adb2e2c16a894c695e13 +# hadolint ignore=DL4006 +RUN printf '%s %s\n' \ + "$NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256" /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py \ + | sha256sum -c - \ + || { echo "ERROR: patch-hermes-sqlite-temp-store.py hash mismatch (update NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256)" >&2; exit 1; } +RUN /usr/bin/python3 -I /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py \ + && grep -q 'PRAGMA temp_store=MEMORY' /opt/hermes/hermes_state.py \ + && HERMES_HOME="$(mktemp -d)" /opt/hermes/.venv/bin/python -I \ + /opt/nemoclaw-hermes-config/image-build-probes.py session-delete + # Hermes v0.19.0 forces the gateway-created Discord recovery ledger to 0600, # which prevents sandbox-owned online backup and makes a sandbox-restored # database unwritable by the gateway. The final layout uses a gateway:sandbox diff --git a/agents/hermes/image-build-probes.py b/agents/hermes/image-build-probes.py index d9875433458..80c8329bb2b 100644 --- a/agents/hermes/image-build-probes.py +++ b/agents/hermes/image-build-probes.py @@ -194,6 +194,20 @@ def verify_session_preview() -> None: assert rows[0]["preview"] == "NEMOCLAW_PREVIEW_LATEST", rows +def verify_session_delete() -> None: + from hermes_state import SessionDB + + db = SessionDB() + session_id = "nemoclaw-session-delete-smoke" + db.create_session(session_id, "cli") + db.append_message(session_id, "user", "probe message 1") + db.append_message(session_id, "assistant", "probe reply") + deleted = db.delete_session(session_id) + assert deleted, f"delete_session returned {deleted!r}" + rows = db.list_sessions_rich(limit=10) + assert not any(r["id"] == session_id for r in rows), "session still present after delete" + + def verify_discord_recovery_source() -> None: source = Path("/opt/hermes/plugins/platforms/discord/recovery.py").read_text( encoding="utf-8" @@ -389,6 +403,7 @@ def reopen_probe(conn): "langfuse-credentials": verify_langfuse_credentials, "neutral-platform-inertness": verify_neutral_platform_inertness, "profile-policy": verify_profile_policy, + "session-delete": verify_session_delete, "session-preview": verify_session_preview, } diff --git a/agents/hermes/patch-hermes-sqlite-temp-store.py b/agents/hermes/patch-hermes-sqlite-temp-store.py new file mode 100755 index 00000000000..f675b01376e --- /dev/null +++ b/agents/hermes/patch-hermes-sqlite-temp-store.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Patch SessionDB.__init__ to use in-memory temp store for SQLite FK processing. + +Source-of-truth note for this localized Hermes runtime patch: + - Invalid state: Hermes v0.19.0 SessionDB does not set PRAGMA temp_store=MEMORY, + so SQLite falls back to file-based temp storage when processing FK constraints + (for example, the ON DELETE CASCADE on session_model_usage -> sessions). When + `hermes sessions delete` is invoked through OpenShell sandbox execution — + the code path used by `nemohermes sessions delete ` — the + process runs in a restricted environment where SQLite's temp-file creation + syscalls fail with + SQLITE_CANTOPEN, causing every `DELETE FROM sessions` with FK enforcement + enabled to raise `sqlite3.OperationalError: unable to open database file` + (#8301). The same command succeeds through Docker execution because that + context allows the file-based temp store. + - Value being patched: pinned/prebuilt `/opt/hermes/hermes_state.py` + `SessionDB.__init__` connection setup block; specifically, the statement + immediately following `apply_wal_with_fallback()` that enables FK enforcement. + `PRAGMA temp_store=MEMORY` is inserted before `PRAGMA foreign_keys=ON` so the + in-memory store is active before any FK-constrained write. + - Source-fix constraint: NemoClaw layers a sandbox image on top of the + published Hermes runtime; the source fix belongs upstream in Hermes, not in + NemoClaw's TypeScript or wrapper code. + - Regression evidence: on first application, this patcher accepts exactly one + unpatched connection setup block and no temp-store statement. A later + application accepts exactly one complete patched block with one temp-store + statement. Every other source shape fails without writing. The Dockerfile + checks for the inserted PRAGMA after patching. The image-build + `session-delete` behavior test creates a SessionDB, inserts a session with + messages, and calls `delete_session()` to confirm that SQLite does not raise + OperationalError. + - Removal condition: delete this patch when the pinned Hermes runtime natively + sets `PRAGMA temp_store=MEMORY` (or equivalent) in `SessionDB.__init__`. +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +OLD = ( + 'apply_wal_with_fallback(self._conn, db_label="state.db")\n' + ' self._conn.execute("PRAGMA foreign_keys=ON")' +) +NEW = ( + 'apply_wal_with_fallback(self._conn, db_label="state.db")\n' + ' self._conn.execute("PRAGMA temp_store=MEMORY")\n' + ' self._conn.execute("PRAGMA foreign_keys=ON")' +) +EXPECTED_OCCURRENCES = 1 + + +def patch_file(path: Path) -> None: + source = path.read_text(encoding="utf-8") + old_count = source.count(OLD) + new_count = source.count('self._conn.execute("PRAGMA temp_store=MEMORY")') + patched_count = source.count(NEW) + if ( + old_count == 0 + and new_count == EXPECTED_OCCURRENCES + and patched_count == EXPECTED_OCCURRENCES + ): + return + if old_count != EXPECTED_OCCURRENCES or new_count != 0: + raise SystemExit( + "ERROR: Hermes SessionDB.__init__ connection setup shape changed; " + f"expected {EXPECTED_OCCURRENCES} unpatched block and no temp-store " + f"statements; found {old_count} unpatched blocks, {new_count} temp-store " + f"statements, and {patched_count} complete patched blocks" + ) + path.write_text(source.replace(OLD, NEW), encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "path", + nargs="?", + default="/opt/hermes/hermes_state.py", + help="Hermes state module to patch", + ) + args = parser.parse_args() + patch_file(Path(args.path)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/lib/onboard/managed-startup/profile.ts b/src/lib/onboard/managed-startup/profile.ts index 88c7dd21380..aea7ffdb22e 100644 --- a/src/lib/onboard/managed-startup/profile.ts +++ b/src/lib/onboard/managed-startup/profile.ts @@ -762,6 +762,7 @@ export const MANAGED_STARTUP_PROFILE_EXCLUDED_DOCKER_INPUTS = { { input: "NEMOCLAW_HERMES_IMAGE_BUILD_PROBES_SHA256", reason: "integrity-pin" }, { input: "NEMOCLAW_HERMES_CRON_EXECUTIONS_SOURCE_SHA256", reason: "integrity-pin" }, { input: "NEMOCLAW_HERMES_BACKUP_SOURCE_SHA256", reason: "integrity-pin" }, + { input: "NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256", reason: "integrity-pin" }, { input: "NEMOCLAW_HERMES_DISCORD_RECOVERY_PATCHER_SHA256", reason: "integrity-pin" }, { input: "NEMOCLAW_HERMES_LANGFUSE_PATCHER_SHA256", reason: "integrity-pin" }, { input: "NEMOCLAW_HERMES_WRAPPER_SHA256", reason: "integrity-pin" }, diff --git a/test/e2e/live/hermes-cli-adapter-live.ts b/test/e2e/live/hermes-cli-adapter-live.ts index 86e7f8fc2da..9e958149797 100644 --- a/test/e2e/live/hermes-cli-adapter-live.ts +++ b/test/e2e/live/hermes-cli-adapter-live.ts @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import { resultText, shellQuote } from "../fixtures/clients/command.ts"; +import type { HostCliClient } from "../fixtures/clients/host.ts"; import { type SandboxClient, trustedSandboxShellScript } from "../fixtures/clients/sandbox.ts"; import { expect } from "../fixtures/e2e-test.ts"; import { exportHermesSession, hermesLastActive } from "../fixtures/hermes-session.ts"; interface HermesCliAdapterLiveOptions { env: NodeJS.ProcessEnv; + host: HostCliClient; redactionValues: string[]; sandbox: SandboxClient; sandboxName: string; @@ -29,6 +31,7 @@ export function onlyNewHermesSessionId(before: Set, after: Set): export async function assertHermesCliAdapterLiveContract({ env, + host, redactionValues, sandbox, sandboxName, @@ -119,6 +122,29 @@ export async function assertHermesCliAdapterLiveContract({ }, ); + const deleteSession = await host.command( + "nemohermes", + [sandboxName, "sessions", "delete", seedSessionId], + { + artifactName: "phase-4-issue-8301-delete-session", + env, + redactionValues, + timeoutMs: 60_000, + }, + ); + expect(deleteSession.exitCode, resultText(deleteSession)).toBe(0); + const sessionsAfterDelete = await host.command("nemohermes", [sandboxName, "sessions", "list"], { + artifactName: "phase-4-issue-8301-sessions-after-delete", + env, + redactionValues, + timeoutMs: 60_000, + }); + expect(sessionsAfterDelete.exitCode, resultText(sessionsAfterDelete)).toBe(0); + expect( + hermesSessionIds(resultText(sessionsAfterDelete)).has(seedSessionId), + stripAnsi(resultText(sessionsAfterDelete)), + ).toBe(false); + const usageFilePath = `/tmp/nemoclaw-cli-adapter-usage-${Date.now()}.json`; const sessionsBeforeGuardedUsage = await listDefaultSessions( "phase-4-cli-adapter-sessions-before-guarded-usage", diff --git a/test/e2e/live/hermes-e2e.test.ts b/test/e2e/live/hermes-e2e.test.ts index a903297e3b3..980502e82af 100644 --- a/test/e2e/live/hermes-e2e.test.ts +++ b/test/e2e/live/hermes-e2e.test.ts @@ -502,6 +502,7 @@ test("hermes-e2e: install.sh onboards Hermes and proves health plus live inferen await assertHermesCliAdapterLiveContract({ env: commandEnv(), + host, redactionValues, sandbox, sandboxName: SANDBOX_NAME, diff --git a/test/hermes-doctor-config-hash.test.ts b/test/hermes-doctor-config-hash.test.ts index c0b9aa8d292..93bcd40fbd0 100644 --- a/test/hermes-doctor-config-hash.test.ts +++ b/test/hermes-doctor-config-hash.test.ts @@ -141,6 +141,7 @@ describe("Hermes doctor and config hash boundary", () => { path.join(libDir, "gateway-supervisor.sh"), path.join(libDir, "validate-hermes-env-secret-boundary.py"), path.join(libDir, "patch-hermes-session-list-preview.py"), + path.join(libDir, "patch-hermes-sqlite-temp-store.py"), discordRecoveryPatcherPath, profilePolicyPatcherPath, managedPolicyReaderPath, diff --git a/test/hermes-final-image-layout.test.ts b/test/hermes-final-image-layout.test.ts index b566d0656e0..77e4b250bb5 100644 --- a/test/hermes-final-image-layout.test.ts +++ b/test/hermes-final-image-layout.test.ts @@ -19,6 +19,11 @@ const HERMES_INTEGRITY_FILES = [ source: "agents/hermes/image-build-probes.py", target: "/opt/nemoclaw-hermes-config/image-build-probes.py", }, + { + arg: "NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256", + source: "agents/hermes/patch-hermes-sqlite-temp-store.py", + target: "/usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py", + }, { arg: "NEMOCLAW_HERMES_WRAPPER_SHA256", source: "agents/hermes/hermes-wrapper.py", @@ -277,6 +282,7 @@ describe("Hermes final image layout", () => { "COPY scripts/managed-gateway-control.py /usr/local/lib/nemoclaw/managed-gateway-control.py", "COPY agents/hermes/validate-env-secret-boundary.py /usr/local/lib/nemoclaw/validate-hermes-env-secret-boundary.py", "COPY agents/hermes/patch-session-list-preview.py /usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py", + "COPY agents/hermes/patch-hermes-sqlite-temp-store.py /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py", "COPY agents/hermes/patch-discord-recovery-permissions.py /usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py", "COPY agents/hermes/patch-profile-policy-defaults.py /usr/local/lib/nemoclaw/patch-hermes-profile-policy-defaults.py", "COPY agents/hermes/managed_policy.py /usr/local/lib/nemoclaw/managed_policy.py", diff --git a/test/hermes-sqlite-temp-store-patch.test.ts b/test/hermes-sqlite-temp-store-patch.test.ts new file mode 100644 index 00000000000..944f019bc59 --- /dev/null +++ b/test/hermes-sqlite-temp-store-patch.test.ts @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { afterEach, describe, expect, it } from "vitest"; + +const root = path.join(import.meta.dirname, ".."); +const patcher = path.join(root, "agents", "hermes", "patch-hermes-sqlite-temp-store.py"); +const dockerfile = fs.readFileSync(path.join(root, "agents", "hermes", "Dockerfile"), "utf8"); +const fixtures: string[] = []; + +const walSetup = 'apply_wal_with_fallback(self._conn, db_label="state.db")'; +const tempStore = ' self._conn.execute("PRAGMA temp_store=MEMORY")'; +const foreignKeys = ' self._conn.execute("PRAGMA foreign_keys=ON")'; +const unpatchedSource = `${walSetup}\n${foreignKeys}\n`; +const patchedSource = `${walSetup}\n${tempStore}\n${foreignKeys}\n`; + +function fixtureFile(source: string): string { + const fixture = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-hermes-sqlite-temp-store-")); + fixtures.push(fixture); + const stateModule = path.join(fixture, "hermes_state.py"); + fs.writeFileSync(stateModule, source); + return stateModule; +} + +function runPatcher(stateModule: string) { + return spawnSync("python3", ["-I", patcher, stateModule], { + encoding: "utf8", + timeout: 5000, + }); +} + +afterEach(() => { + for (const fixture of fixtures.splice(0)) { + fs.rmSync(fixture, { recursive: true, force: true }); + } +}); + +describe("Hermes SQLite temp-store patch", () => { + it("inserts in-memory temp storage before foreign-key enforcement (#8301)", () => { + const stateModule = fixtureFile(unpatchedSource); + + const result = runPatcher(stateModule); + + expect(result.status, result.stderr).toBe(0); + expect(fs.readFileSync(stateModule, "utf8")).toBe(patchedSource); + }); + + it("accepts one already-patched connection block (#8301)", () => { + const stateModule = fixtureFile(patchedSource); + + const result = runPatcher(stateModule); + + expect(result.status, result.stderr).toBe(0); + expect(fs.readFileSync(stateModule, "utf8")).toBe(patchedSource); + }); + + it.each([ + ["duplicate", `${patchedSource}${tempStore}\n`], + ["partial", `${walSetup}\n${tempStore}\n`], + ["misplaced", `${tempStore}\n${unpatchedSource}`], + ])("rejects a %s temp-store patch (#8301)", (_case, source) => { + const stateModule = fixtureFile(source); + + const result = runPatcher(stateModule); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("Hermes SessionDB.__init__ connection setup shape changed"); + expect(fs.readFileSync(stateModule, "utf8")).toBe(source); + }); + + it("binds the Hermes image to the reviewed patcher (#8301)", () => { + const digest = createHash("sha256").update(fs.readFileSync(patcher)).digest("hex"); + + expect(dockerfile).toContain(`ARG NEMOCLAW_HERMES_SQLITE_TEMP_STORE_PATCHER_SHA256=${digest}`); + expect(dockerfile).toContain( + "COPY agents/hermes/patch-hermes-sqlite-temp-store.py " + + "/usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py", + ); + expect(dockerfile).toContain( + "RUN /usr/bin/python3 -I /usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py", + ); + }); +}); diff --git a/test/sandbox-provisioning.test.ts b/test/sandbox-provisioning.test.ts index 52d42c3585a..09b126df6fa 100644 --- a/test/sandbox-provisioning.test.ts +++ b/test/sandbox-provisioning.test.ts @@ -1057,6 +1057,7 @@ describe("Hermes sandbox provisioning", () => { path.join(localLib, "sandbox-init.sh"), path.join(localLib, "validate-hermes-env-secret-boundary.py"), path.join(localLib, "patch-hermes-session-list-preview.py"), + path.join(localLib, "patch-hermes-sqlite-temp-store.py"), path.join(localLib, "patch-hermes-discord-recovery-permissions.py"), path.join(localLib, "patch-hermes-profile-policy-defaults.py"), managedPolicyReaderPath, diff --git a/test/sandbox-rlimit-hooks.test.ts b/test/sandbox-rlimit-hooks.test.ts index db49265afd6..f55ed15c715 100644 --- a/test/sandbox-rlimit-hooks.test.ts +++ b/test/sandbox-rlimit-hooks.test.ts @@ -512,6 +512,7 @@ describe("sandbox rlimit system hooks (#2173)", () => { const initLib = path.join(localLib, "sandbox-init.sh"); const validator = path.join(localLib, "validate-hermes-env-secret-boundary.py"); const sessionListPreviewPatcher = path.join(localLib, "patch-hermes-session-list-preview.py"); + const sqliteTempStorePatcher = path.join(localLib, "patch-hermes-sqlite-temp-store.py"); const discordRecoveryPatcher = path.join( localLib, "patch-hermes-discord-recovery-permissions.py", @@ -551,6 +552,7 @@ describe("sandbox rlimit system hooks (#2173)", () => { fs.writeFileSync(initLib, "# init fixture\n"); fs.writeFileSync(validator, "# validator fixture\n"); fs.writeFileSync(sessionListPreviewPatcher, "# session list preview patcher fixture\n"); + fs.writeFileSync(sqliteTempStorePatcher, "# SQLite temp store patcher fixture\n"); fs.writeFileSync(discordRecoveryPatcher, "# Discord recovery patcher fixture\n"); fs.writeFileSync(profilePolicyPatcher, "# profile policy patcher fixture\n"); fs.writeFileSync(managedPolicyReader, "# managed policy reader fixture\n"); @@ -596,6 +598,10 @@ describe("sandbox rlimit system hooks (#2173)", () => { "/usr/local/lib/nemoclaw/patch-hermes-session-list-preview.py", sessionListPreviewPatcher, ) + .replaceAll( + "/usr/local/lib/nemoclaw/patch-hermes-sqlite-temp-store.py", + sqliteTempStorePatcher, + ) .replaceAll( "/usr/local/lib/nemoclaw/patch-hermes-discord-recovery-permissions.py", discordRecoveryPatcher,