diff --git a/src/commands/cleanup-utils.test.ts b/src/commands/cleanup-utils.test.ts index 1df18875eb..eeaf02ae4e 100644 --- a/src/commands/cleanup-utils.test.ts +++ b/src/commands/cleanup-utils.test.ts @@ -1,26 +1,31 @@ +import path from "node:path"; import { describe, expect, test } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { buildCleanupPlan } from "./cleanup-utils.js"; describe("buildCleanupPlan", () => { test("resolves inside-state flags and workspace dirs", () => { + const tmpRoot = path.join(path.parse(process.cwd()).root, "tmp"); const cfg = { agents: { - defaults: { workspace: "/tmp/openclaw-workspace-1" }, - list: [{ workspace: "/tmp/openclaw-workspace-2" }], + defaults: { workspace: path.join(tmpRoot, "openclaw-workspace-1") }, + list: [{ workspace: path.join(tmpRoot, "openclaw-workspace-2") }], }, }; const plan = buildCleanupPlan({ cfg: cfg as unknown as OpenClawConfig, - stateDir: "/tmp/openclaw-state", - configPath: "/tmp/openclaw-state/openclaw.json", - oauthDir: "/tmp/openclaw-oauth", + stateDir: path.join(tmpRoot, "openclaw-state"), + configPath: path.join(tmpRoot, "openclaw-state", "openclaw.json"), + oauthDir: path.join(tmpRoot, "openclaw-oauth"), }); expect(plan.configInsideState).toBe(true); expect(plan.oauthInsideState).toBe(false); expect(new Set(plan.workspaceDirs)).toEqual( - new Set(["/tmp/openclaw-workspace-1", "/tmp/openclaw-workspace-2"]), + new Set([ + path.join(tmpRoot, "openclaw-workspace-1"), + path.join(tmpRoot, "openclaw-workspace-2"), + ]), ); }); }); diff --git a/src/discord/monitor/exec-approvals.test.ts b/src/discord/monitor/exec-approvals.test.ts index 9d2685874f..5992b42200 100644 --- a/src/discord/monitor/exec-approvals.test.ts +++ b/src/discord/monitor/exec-approvals.test.ts @@ -1,6 +1,8 @@ import type { ButtonInteraction, ComponentData } from "@buape/carbon"; import { Routes } from "discord-api-types/v10"; import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { DiscordExecApprovalConfig } from "../../config/types.discord.js"; import { @@ -13,7 +15,7 @@ import { type ExecApprovalButtonContext, } from "./exec-approvals.js"; -const STORE_PATH = "/tmp/openclaw-exec-approvals-test.json"; +const STORE_PATH = path.join(os.tmpdir(), "openclaw-exec-approvals-test.json"); const writeStore = (store: Record) => { fs.writeFileSync(STORE_PATH, `${JSON.stringify(store, null, 2)}\n`, "utf8"); diff --git a/src/media/local-roots.ts b/src/media/local-roots.ts index b43e2d0f53..ec4943e878 100644 --- a/src/media/local-roots.ts +++ b/src/media/local-roots.ts @@ -2,25 +2,28 @@ import os from "node:os"; import path from "node:path"; import type { OpenClawConfig } from "../config/config.js"; import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; -import { STATE_DIR } from "../config/paths.js"; +import { resolveStateDir } from "../config/paths.js"; -const STATIC_LOCAL_ROOTS = [ - os.tmpdir(), - path.join(STATE_DIR, "media"), - path.join(STATE_DIR, "agents"), - path.join(STATE_DIR, "workspace"), - path.join(STATE_DIR, "sandboxes"), -] as const; +function buildMediaLocalRoots(stateDir: string): string[] { + const resolvedStateDir = path.resolve(stateDir); + return [ + os.tmpdir(), + path.join(resolvedStateDir, "media"), + path.join(resolvedStateDir, "agents"), + path.join(resolvedStateDir, "workspace"), + path.join(resolvedStateDir, "sandboxes"), + ]; +} export function getDefaultMediaLocalRoots(): readonly string[] { - return STATIC_LOCAL_ROOTS; + return buildMediaLocalRoots(resolveStateDir()); } export function getAgentScopedMediaLocalRoots( cfg: OpenClawConfig, agentId?: string, ): readonly string[] { - const roots = [...STATIC_LOCAL_ROOTS]; + const roots = buildMediaLocalRoots(resolveStateDir()); if (!agentId?.trim()) { return roots; } diff --git a/src/web/media.test.ts b/src/web/media.test.ts index 86fe6c59c0..17e4f76ca8 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -19,6 +19,7 @@ let alphaPngFile = ""; let fallbackPngBuffer: Buffer; let fallbackPngFile = ""; let fallbackPngCap = 0; +let previousStateDir: string | undefined; async function writeTempFile(buffer: Buffer, ext: string): Promise { const file = path.join(fixtureRoot, `media-${fixtureFileCount++}${ext}`); @@ -96,6 +97,21 @@ afterEach(() => { }); describe("web media loading", () => { + beforeAll(() => { + // Ensure state dir is stable and not influenced by other tests that stub OPENCLAW_STATE_DIR. + // Also keep it outside os.tmpdir() so tmpdir localRoots doesn't accidentally make all state readable. + previousStateDir = process.env.OPENCLAW_STATE_DIR; + process.env.OPENCLAW_STATE_DIR = path.join(os.homedir(), ".openclaw-media-state-test"); + }); + + afterAll(() => { + if (previousStateDir === undefined) { + delete process.env.OPENCLAW_STATE_DIR; + } else { + process.env.OPENCLAW_STATE_DIR = previousStateDir; + } + }); + beforeAll(() => { vi.spyOn(ssrf, "resolvePinnedHostname").mockImplementation(async (hostname) => { const normalized = hostname.trim().toLowerCase().replace(/\.$/, ""); @@ -346,11 +362,12 @@ describe("local media root guard", () => { }); it("allows default OpenClaw state workspace and sandbox roots", async () => { - const { STATE_DIR } = await import("../config/paths.js"); + const { resolveStateDir } = await import("../config/paths.js"); + const stateDir = resolveStateDir(); const readFile = vi.fn(async () => Buffer.from("generated-media")); await expect( - loadWebMedia(path.join(STATE_DIR, "workspace", "tmp", "render.bin"), { + loadWebMedia(path.join(stateDir, "workspace", "tmp", "render.bin"), { maxBytes: 1024 * 1024, readFile, }), @@ -361,7 +378,7 @@ describe("local media root guard", () => { ); await expect( - loadWebMedia(path.join(STATE_DIR, "sandboxes", "session-1", "frame.bin"), { + loadWebMedia(path.join(stateDir, "sandboxes", "session-1", "frame.bin"), { maxBytes: 1024 * 1024, readFile, }), @@ -373,11 +390,12 @@ describe("local media root guard", () => { }); it("rejects default OpenClaw state per-agent workspace-* roots without explicit local roots", async () => { - const { STATE_DIR } = await import("../config/paths.js"); + const { resolveStateDir } = await import("../config/paths.js"); + const stateDir = resolveStateDir(); const readFile = vi.fn(async () => Buffer.from("generated-media")); await expect( - loadWebMedia(path.join(STATE_DIR, "workspace-clawdy", "tmp", "render.bin"), { + loadWebMedia(path.join(stateDir, "workspace-clawdy", "tmp", "render.bin"), { maxBytes: 1024 * 1024, readFile, }), @@ -385,9 +403,10 @@ describe("local media root guard", () => { }); it("allows per-agent workspace-* paths with explicit local roots", async () => { - const { STATE_DIR } = await import("../config/paths.js"); + const { resolveStateDir } = await import("../config/paths.js"); + const stateDir = resolveStateDir(); const readFile = vi.fn(async () => Buffer.from("generated-media")); - const agentWorkspaceDir = path.join(STATE_DIR, "workspace-clawdy"); + const agentWorkspaceDir = path.join(stateDir, "workspace-clawdy"); await expect( loadWebMedia(path.join(agentWorkspaceDir, "tmp", "render.bin"), {