Merge 52b13ac101cf8f2d1147af4c9c8f7d63ada5e16a into 8a05c05596ca9ba0735dafd8e359885de4c2c969
This commit is contained in:
commit
82c5799066
@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import fsSync from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { withTempDir } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
resolveDefaultConfigCandidates,
|
||||
@ -141,3 +142,59 @@ describe("state + config path candidates", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveStateDir nesting guard (#45765)", () => {
|
||||
it("does not append .openclaw when OPENCLAW_HOME ends with .openclaw", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.openclaw" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/home/user/.openclaw"));
|
||||
});
|
||||
|
||||
it("does not append .openclaw when OPENCLAW_HOME ends with .clawdbot", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.clawdbot" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/home/user/.clawdbot"));
|
||||
});
|
||||
|
||||
it("does not append .openclaw when OPENCLAW_HOME ends with .moldbot", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.moldbot" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/home/user/.moldbot"));
|
||||
});
|
||||
|
||||
it("handles tilde expansion when OPENCLAW_HOME=~/.openclaw", () => {
|
||||
const env = { OPENCLAW_HOME: "~/.openclaw", HOME: "/home/user" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/home/user/.openclaw"));
|
||||
});
|
||||
|
||||
it("still appends .openclaw when OPENCLAW_HOME is not a state dir basename", () => {
|
||||
const env = { OPENCLAW_HOME: "/srv/app" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/srv/app/.openclaw"));
|
||||
});
|
||||
|
||||
it("still appends .openclaw when OPENCLAW_HOME is not set", () => {
|
||||
const env = { HOME: "/home/user" } as NodeJS.ProcessEnv;
|
||||
expect(resolveStateDir(env)).toBe(path.resolve("/home/user/.openclaw"));
|
||||
});
|
||||
|
||||
it("preserves existing nested state dir for backward compat", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.openclaw" } as NodeJS.ProcessEnv;
|
||||
const nestedDir = path.resolve("/home/user/.openclaw/.openclaw");
|
||||
vi.spyOn(fsSync, "existsSync").mockImplementation((p) => String(p) === nestedDir);
|
||||
expect(resolveStateDir(env)).toBe(nestedDir);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("preserves existing nested legacy dir when OPENCLAW_HOME ends in legacy basename", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.clawdbot" } as NodeJS.ProcessEnv;
|
||||
const nestedDir = path.resolve("/home/user/.clawdbot/.clawdbot");
|
||||
vi.spyOn(fsSync, "existsSync").mockImplementation((p) => String(p) === nestedDir);
|
||||
expect(resolveStateDir(env)).toBe(nestedDir);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("preserves nested .moldbot when OPENCLAW_HOME ends in .clawdbot", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.clawdbot" } as NodeJS.ProcessEnv;
|
||||
const nestedDir = path.resolve("/home/user/.clawdbot/.moldbot");
|
||||
vi.spyOn(fsSync, "existsSync").mockImplementation((p) => String(p) === nestedDir);
|
||||
expect(resolveStateDir(env)).toBe(nestedDir);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
@ -21,6 +21,12 @@ export const isNixMode = resolveIsNixMode();
|
||||
const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moldbot", ".moltbot"] as const;
|
||||
const NEW_STATE_DIRNAME = ".openclaw";
|
||||
const CONFIG_FILENAME = "openclaw.json";
|
||||
|
||||
/** All recognized state directory basenames (for nesting guard). */
|
||||
export const ALL_STATE_DIRNAMES: ReadonlySet<string> = new Set([
|
||||
NEW_STATE_DIRNAME,
|
||||
...LEGACY_STATE_DIRNAMES,
|
||||
]);
|
||||
const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moldbot.json", "moltbot.json"] as const;
|
||||
|
||||
function resolveDefaultHomeDir(): string {
|
||||
@ -66,6 +72,28 @@ export function resolveStateDir(
|
||||
if (override) {
|
||||
return resolveUserPath(override, env, effectiveHomedir);
|
||||
}
|
||||
// Nesting guard: when OPENCLAW_HOME is explicitly set and its basename is
|
||||
// already a known state directory name, use it directly without appending.
|
||||
const explicitHome = env.OPENCLAW_HOME?.trim();
|
||||
if (explicitHome) {
|
||||
const resolvedHome = effectiveHomedir();
|
||||
if (ALL_STATE_DIRNAMES.has(path.basename(resolvedHome))) {
|
||||
// Backward compat: if a nested state dir already exists from the old
|
||||
// buggy behavior, prefer it so we don't orphan existing state data.
|
||||
// Check all known state dirnames, not just .openclaw.
|
||||
for (const nestedName of ALL_STATE_DIRNAMES) {
|
||||
const nestedState = path.join(resolvedHome, nestedName);
|
||||
try {
|
||||
if (fs.existsSync(nestedState)) {
|
||||
return nestedState;
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
return resolvedHome;
|
||||
}
|
||||
}
|
||||
const newDir = newStateDir(effectiveHomedir);
|
||||
if (env.OPENCLAW_TEST_FAST === "1") {
|
||||
return newDir;
|
||||
|
||||
@ -139,6 +139,38 @@ describe("resolveConfigDir", () => {
|
||||
|
||||
expect(resolveConfigDir(env)).toBe(path.resolve("/tmp/openclaw-home", "state"));
|
||||
});
|
||||
|
||||
it("does not append .openclaw when OPENCLAW_HOME ends with .openclaw (#45765)", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.openclaw" } as NodeJS.ProcessEnv;
|
||||
expect(resolveConfigDir(env)).toBe(path.resolve("/home/user/.openclaw"));
|
||||
});
|
||||
|
||||
it("does not append .openclaw when OPENCLAW_HOME ends with .clawdbot (#45765)", () => {
|
||||
const env = { OPENCLAW_HOME: "/home/user/.clawdbot" } as NodeJS.ProcessEnv;
|
||||
expect(resolveConfigDir(env)).toBe(path.resolve("/home/user/.clawdbot"));
|
||||
});
|
||||
|
||||
it("handles tilde expansion when OPENCLAW_HOME=~/.openclaw (#45765)", () => {
|
||||
const env = { OPENCLAW_HOME: "~/.openclaw", HOME: "/home/user" } as NodeJS.ProcessEnv;
|
||||
expect(resolveConfigDir(env)).toBe(path.resolve("/home/user/.openclaw"));
|
||||
});
|
||||
|
||||
it("still appends .openclaw when OPENCLAW_HOME is not a state dir basename", () => {
|
||||
const env = { OPENCLAW_HOME: "/srv/app" } as NodeJS.ProcessEnv;
|
||||
expect(resolveConfigDir(env)).toBe(path.resolve("/srv/app/.openclaw"));
|
||||
});
|
||||
|
||||
it("prefers existing nested config dir for backward compat (#45765)", () => {
|
||||
const tmpDir = path.join(os.tmpdir(), `openclaw-test-config-${Date.now()}`);
|
||||
const nestedDir = path.join(tmpDir, ".openclaw", ".openclaw");
|
||||
fs.mkdirSync(nestedDir, { recursive: true });
|
||||
try {
|
||||
const env = { OPENCLAW_HOME: path.join(tmpDir, ".openclaw") } as NodeJS.ProcessEnv;
|
||||
expect(resolveConfigDir(env)).toBe(path.join(tmpDir, ".openclaw", ".openclaw"));
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveHomeDir", () => {
|
||||
|
||||
21
src/utils.ts
21
src/utils.ts
@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveOAuthDir } from "./config/paths.js";
|
||||
import { ALL_STATE_DIRNAMES, resolveOAuthDir } from "./config/paths.js";
|
||||
import { logVerbose, shouldLogVerbose } from "./globals.js";
|
||||
import {
|
||||
resolveEffectiveHomeDir,
|
||||
@ -290,7 +290,24 @@ export function resolveConfigDir(
|
||||
if (override) {
|
||||
return resolveUserPath(override, env, homedir);
|
||||
}
|
||||
const newDir = path.join(resolveRequiredHomeDir(env, homedir), ".openclaw");
|
||||
// Nesting guard: when OPENCLAW_HOME is explicitly set and its basename is
|
||||
// already a known state directory name, use it directly without appending.
|
||||
const explicitHome = env.OPENCLAW_HOME?.trim();
|
||||
const resolvedHome = resolveRequiredHomeDir(env, homedir);
|
||||
if (explicitHome && ALL_STATE_DIRNAMES.has(path.basename(resolvedHome))) {
|
||||
// Backward compat: if a nested config dir already exists from the old
|
||||
// buggy behavior, prefer it so we don't orphan existing config data.
|
||||
const nestedConfig = path.join(resolvedHome, ".openclaw");
|
||||
try {
|
||||
if (fs.existsSync(nestedConfig)) {
|
||||
return nestedConfig;
|
||||
}
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
return resolvedHome;
|
||||
}
|
||||
const newDir = path.join(resolvedHome, ".openclaw");
|
||||
try {
|
||||
const hasNew = fs.existsSync(newDir);
|
||||
if (hasNew) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user