mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(infra): skip POSIX tmp path on Windows (#73533)
Skip the POSIX `/tmp/openclaw` preferred path on Windows so temp files land under the trusted `os.tmpdir()`/`%TEMP%`-based `openclaw-<uid>` path instead of `C:\tmp\openclaw`. Add regression coverage for Windows path selection and the WhatsApp media temp directory integration, plus a changelog entry. Fixes #60713. Tests: - pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/infra/tmp-openclaw-dir.ts src/infra/tmp-openclaw-dir.test.ts extensions/whatsapp/src/media.test.ts - pnpm test src/infra/tmp-openclaw-dir.test.ts extensions/whatsapp/src/media.test.ts - pnpm check:changed Thanks @juan-flores077. Co-authored-by: Juan Flores <112629487+juan-flores077@users.noreply.github.com> Co-authored-by: Brad Groux <3053586+BradGroux@users.noreply.github.com>
This commit is contained in:
@@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Infra/Windows: skip the POSIX `/tmp/openclaw` preferred path on Windows in `resolvePreferredOpenClawTmpDir` so log files, TTS temp files, and other writes land in `%TEMP%\openclaw-<uid>` instead of `C:\tmp\openclaw`. Fixes #60713. Thanks @juan-flores077.
|
||||
- Media/Windows: open saved attachment temp files read/write before fsync so Windows WebChat and `chat.send` media offloads no longer fail with EPERM during durability flush. (#76593) Thanks @qq230849622-a11y.
|
||||
- Agents/tools: honor narrow runtime tool allowlists when constructing embedded-runner tool families and bundled MCP/LSP runtimes, so cron/subagent runs that request tools such as `update_plan`, `browser`, `x_search`, channel login tools, or `group:plugins` no longer start with missing tools or unrelated bootstrap work. (#77519, #77532)
|
||||
- Codex plugin: mirror the experimental upstream app-server protocol and format generated TypeScript before drift checks, keeping OpenClaw's `experimentalApi` bridge compatible with latest Codex while preserving formatter gates.
|
||||
|
||||
@@ -352,6 +352,10 @@ describe("local media root guard", () => {
|
||||
const actualLstat = await fs.lstat(tinyPngFile);
|
||||
const actualStat = await fs.stat(tinyPngFile);
|
||||
const zeroDev = typeof actualLstat.dev === "bigint" ? 0n : 0;
|
||||
// Resolve before mocking platform: under `win32` the helper returns the
|
||||
// os.tmpdir() fallback rather than the POSIX `/tmp/openclaw` root that
|
||||
// actually holds `tinyPngFile` on this Linux test runner (#60713).
|
||||
const realTmpRoot = resolvePreferredOpenClawTmpDir();
|
||||
|
||||
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
|
||||
const lstatSpy = vi
|
||||
@@ -361,7 +365,7 @@ describe("local media root guard", () => {
|
||||
|
||||
try {
|
||||
const result = await loadWebMedia(tinyPngFile, 1024 * 1024, {
|
||||
localRoots: [resolvePreferredOpenClawTmpDir()],
|
||||
localRoots: [realTmpRoot],
|
||||
});
|
||||
expect(result.kind).toBe("image");
|
||||
expect(result.buffer.length).toBeGreaterThan(0);
|
||||
|
||||
@@ -512,4 +512,53 @@ describe("resolvePreferredOpenClawTmpDir", () => {
|
||||
}),
|
||||
).toThrow(/Unable to create fallback OpenClaw temp dir/);
|
||||
});
|
||||
|
||||
it("skips the POSIX preferred path on Windows even when /tmp is accessible (#60713)", () => {
|
||||
// Node on Windows resolves the POSIX path `/tmp` to `C:\tmp` against the
|
||||
// current drive root. If `C:\tmp` happens to exist (Git, MSYS2, etc.
|
||||
// create it), the previous code path returned `/tmp/openclaw` and routed
|
||||
// log files / TTS temp files there instead of `%TEMP%\openclaw`. The
|
||||
// platform: "win32" branch must skip the POSIX path entirely.
|
||||
const winFallback = path.win32.join("C:\\Users\\u\\AppData\\Local\\Temp", "openclaw-501");
|
||||
const accessSync = vi.fn();
|
||||
const lstatSync = vi.fn((target: string) => {
|
||||
if (target === POSIX_OPENCLAW_TMP_DIR || target === winFallback) {
|
||||
return secureDirStat();
|
||||
}
|
||||
throw nodeErrorWithCode("ENOENT");
|
||||
});
|
||||
const mkdirSync = vi.fn();
|
||||
const chmodSync = vi.fn();
|
||||
const tmpdir = vi.fn(() => "C:\\Users\\u\\AppData\\Local\\Temp");
|
||||
|
||||
const result = resolvePreferredOpenClawTmpDir({
|
||||
platform: "win32",
|
||||
accessSync,
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
chmodSync,
|
||||
getuid: vi.fn(() => 501),
|
||||
tmpdir,
|
||||
warn: vi.fn(),
|
||||
});
|
||||
|
||||
expect(result).toBe(winFallback);
|
||||
expect(result).not.toBe(POSIX_OPENCLAW_TMP_DIR);
|
||||
expect(tmpdir).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("still uses the POSIX preferred path on non-Windows platforms when available", () => {
|
||||
const result = resolvePreferredOpenClawTmpDir({
|
||||
platform: "linux",
|
||||
accessSync: vi.fn(),
|
||||
lstatSync: vi.fn(() => secureDirStat()),
|
||||
mkdirSync: vi.fn(),
|
||||
chmodSync: vi.fn(),
|
||||
getuid: vi.fn(() => 501),
|
||||
tmpdir: vi.fn(() => "/var/fallback"),
|
||||
warn: vi.fn(),
|
||||
});
|
||||
|
||||
expect(result).toBe(POSIX_OPENCLAW_TMP_DIR);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,8 +30,13 @@ function isNodeErrorWithCode(err: unknown, code: string): err is MaybeNodeError
|
||||
);
|
||||
}
|
||||
|
||||
type ResolvePreferredOpenClawTmpDirInternalOptions = ResolvePreferredOpenClawTmpDirOptions & {
|
||||
/** Test seam for the host platform; defaults to `process.platform`. */
|
||||
platform?: NodeJS.Platform;
|
||||
};
|
||||
|
||||
export function resolvePreferredOpenClawTmpDir(
|
||||
options: ResolvePreferredOpenClawTmpDirOptions = {},
|
||||
options: ResolvePreferredOpenClawTmpDirInternalOptions = {},
|
||||
): string {
|
||||
// Evaluated here (not at module load) so this file is safe to import in browser bundles.
|
||||
const TMP_DIR_ACCESS_MODE = fs.constants.W_OK | fs.constants.X_OK;
|
||||
@@ -50,6 +55,7 @@ export function resolvePreferredOpenClawTmpDir(
|
||||
}
|
||||
});
|
||||
const tmpdir = typeof options.tmpdir === "function" ? options.tmpdir : getOsTmpDir;
|
||||
const platform = options.platform ?? process.platform;
|
||||
const uid = getuid();
|
||||
|
||||
const isSecureDirForUser = (st: { mode?: number; uid?: number }): boolean => {
|
||||
@@ -69,7 +75,11 @@ export function resolvePreferredOpenClawTmpDir(
|
||||
const fallback = (): string => {
|
||||
const base = tmpdir();
|
||||
const suffix = uid === undefined ? "openclaw" : `openclaw-${uid}`;
|
||||
return path.join(base, suffix);
|
||||
// Use the platform-specific joiner so Windows fallbacks stay in pure
|
||||
// backslash form even when the host process is non-Windows (e.g. when
|
||||
// tests inject `platform: "win32"` on a Linux runner).
|
||||
const joiner = platform === "win32" ? path.win32.join : path.join;
|
||||
return joiner(base, suffix);
|
||||
};
|
||||
|
||||
const isTrustedTmpDir = (st: {
|
||||
@@ -155,6 +165,17 @@ export function resolvePreferredOpenClawTmpDir(
|
||||
return fallbackPath;
|
||||
};
|
||||
|
||||
// On Windows, Node resolves the POSIX path `/tmp` to `C:\tmp` (relative to
|
||||
// the current drive root). Many Windows hosts have `C:\tmp` because Git,
|
||||
// MSYS2, and other Unix-compat tools create it; the existing logic then
|
||||
// happily writes logs and TTS files to `C:\tmp\openclaw\` while every
|
||||
// other code path expects `%TEMP%\openclaw\`. Skip the POSIX preferred
|
||||
// path entirely on Windows so the function falls through to the
|
||||
// os.tmpdir() fallback (#60713).
|
||||
if (platform === "win32") {
|
||||
return ensureTrustedFallbackDir();
|
||||
}
|
||||
|
||||
const existingPreferredState = resolveDirState(POSIX_OPENCLAW_TMP_DIR);
|
||||
if (existingPreferredState === "available") {
|
||||
return POSIX_OPENCLAW_TMP_DIR;
|
||||
|
||||
Reference in New Issue
Block a user