mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 08:31:15 +00:00
fix(exec): stop isolated state dirs from moving live approvals (#108742)
* fix(exec): isolate approval state directories * chore: drop release-owned changelog entry * chore: refresh native i18n inventory * fix(ci): pin XcodeGen for Periphery scans
This commit is contained in:
committed by
GitHub
parent
7a18a781bc
commit
8fe4eeea9e
@@ -183,7 +183,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -205,7 +204,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
observe: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,7 +218,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
observe: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -231,7 +228,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
crossStateDirImports: false,
|
||||
requireStartupMigrationCheckpoint: true,
|
||||
});
|
||||
});
|
||||
@@ -264,7 +260,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -288,7 +283,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -313,7 +307,6 @@ describe("ensureConfigReady", () => {
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
expect(setRuntimeConfigSnapshotMock).toHaveBeenCalledWith(
|
||||
migratedSnapshot.runtimeConfig,
|
||||
@@ -336,7 +329,7 @@ describe("ensureConfigReady", () => {
|
||||
{ commandPath: ["plugins", "list"], source: "exec-approvals.json" },
|
||||
{ commandPath: ["tasks", "list"], source: "plugin-binding-approvals.json" },
|
||||
])(
|
||||
"runs notice-only preflight for $commandPath with default-state $source",
|
||||
"ignores default-state $source while $commandPath uses custom state",
|
||||
async ({ commandPath, source }) => {
|
||||
const root = useTempOpenClawHome();
|
||||
const stateDir = path.join(root, "custom-state");
|
||||
@@ -347,14 +340,7 @@ describe("ensureConfigReady", () => {
|
||||
|
||||
await runEnsureConfigReady(commandPath);
|
||||
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledOnce();
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledWith({
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
...(commandPath[0] === "status" ? { observe: false } : {}),
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).not.toHaveBeenCalled();
|
||||
expect(fs.readFileSync(sourcePath, "utf8")).toBe(sourceRaw);
|
||||
expect(fs.existsSync(`${sourcePath}.migrated`)).toBe(false);
|
||||
expect(fs.existsSync(path.join(stateDir, "exec-approvals.json"))).toBe(false);
|
||||
|
||||
@@ -4,13 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { withSuppressedNotes } from "../../../packages/terminal-core/src/note.js";
|
||||
import { readConfigFileSnapshot, setRuntimeConfigSnapshot } from "../../config/config.js";
|
||||
import {
|
||||
isNamedProfile,
|
||||
resolveLegacyStateDirs,
|
||||
resolveNewStateDir,
|
||||
resolveOAuthDir,
|
||||
resolveStateDir,
|
||||
} from "../../config/paths.js";
|
||||
import { resolveLegacyStateDirs, resolveOAuthDir, resolveStateDir } from "../../config/paths.js";
|
||||
import type { ConfigFileSnapshot } from "../../config/types.js";
|
||||
import { resolveRequiredHomeDir } from "../../infra/home-dir.js";
|
||||
import { ExitError, type RuntimeEnv } from "../../runtime.js";
|
||||
@@ -101,23 +95,6 @@ function hasBundledChannelLegacyStateMigrationInputs(stateDir: string, oauthDir:
|
||||
return dirHasFile(oauthDir, isLegacyWhatsAppAuthFile);
|
||||
}
|
||||
|
||||
function hasCrossStateDirApprovalMigrationInputs(stateDir: string): boolean {
|
||||
if (!process.env.OPENCLAW_STATE_DIR?.trim() || isNamedProfile()) {
|
||||
return false;
|
||||
}
|
||||
const homeDir = resolveRequiredHomeDir(process.env, os.homedir);
|
||||
const defaultStateDir = resolveNewStateDir(() => homeDir);
|
||||
if (path.resolve(defaultStateDir) === path.resolve(stateDir)) {
|
||||
return false;
|
||||
}
|
||||
const execApprovalsSource = path.join(defaultStateDir, "exec-approvals.json");
|
||||
const execApprovalsTarget = path.join(stateDir, "exec-approvals.json");
|
||||
return (
|
||||
(fileOrDirExists(execApprovalsSource) && !fileOrDirExists(execApprovalsTarget)) ||
|
||||
fileOrDirExists(path.join(defaultStateDir, "plugin-binding-approvals.json"))
|
||||
);
|
||||
}
|
||||
|
||||
function hasPendingSqliteSidecarArchive(sourcePath: string): boolean {
|
||||
return (
|
||||
fileOrDirExists(`${sourcePath}.migrated`) &&
|
||||
@@ -153,8 +130,7 @@ function hasLegacyStateMigrationInputs(): boolean {
|
||||
sqliteSidecarPaths.some(
|
||||
(sourcePath) => fileOrDirExists(sourcePath) || hasPendingSqliteSidecarArchive(sourcePath),
|
||||
) ||
|
||||
hasBundledChannelLegacyStateMigrationInputs(stateDir, oauthDir) ||
|
||||
hasCrossStateDirApprovalMigrationInputs(stateDir)
|
||||
hasBundledChannelLegacyStateMigrationInputs(stateDir, oauthDir)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -232,7 +208,6 @@ export async function ensureConfigReady(params: {
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
...(commandName === "status" ? { observe: false } : {}),
|
||||
crossStateDirImports: false,
|
||||
...(shouldRequireStartupMigrationCheckpoint(commandPath)
|
||||
? { requireStartupMigrationCheckpoint: true }
|
||||
: {}),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Register maintenance tests cover maintenance command registration in the CLI program.
|
||||
import { Command } from "commander";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV } from "../../commands/doctor-invocation.js";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerMaintenanceCommands } from "./register.maintenance.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
@@ -69,10 +68,6 @@ describe("registerMaintenanceCommands doctor action", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("exits with code 0 after successful doctor run", async () => {
|
||||
doctorCommand.mockResolvedValue(undefined);
|
||||
|
||||
@@ -106,28 +101,6 @@ describe("registerMaintenanceCommands doctor action", () => {
|
||||
const [runtimeArg, options] = commandCall(doctorCommand);
|
||||
expect(runtimeArg).toBe(runtime);
|
||||
expect(options.repair).toBe(true);
|
||||
expect(options.crossStateDirImports).toBe(true);
|
||||
});
|
||||
|
||||
it("denies cross-state imports when an automation parent disables them", async () => {
|
||||
doctorCommand.mockResolvedValue(undefined);
|
||||
vi.stubEnv(DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV, "1");
|
||||
|
||||
await runMaintenanceCli(["doctor", "--fix", "--non-interactive"]);
|
||||
|
||||
const [, options] = commandCall(doctorCommand);
|
||||
expect(options.repair).toBe(true);
|
||||
expect(options.crossStateDirImports).toBe(false);
|
||||
});
|
||||
|
||||
it("denies cross-state imports for older update parents", async () => {
|
||||
doctorCommand.mockResolvedValue(undefined);
|
||||
vi.stubEnv("OPENCLAW_UPDATE_IN_PROGRESS", "1");
|
||||
|
||||
await runMaintenanceCli(["doctor", "--fix", "--non-interactive"]);
|
||||
|
||||
const [, options] = commandCall(doctorCommand);
|
||||
expect(options.crossStateDirImports).toBe(false);
|
||||
});
|
||||
|
||||
it("passes session sqlite options to doctor command", async () => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { Command } from "commander";
|
||||
import { formatDocsLink } from "../../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { resolveDoctorCrossStateDirImports } from "../../commands/doctor-invocation.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { runCommandWithRuntime } from "../cli-utils.js";
|
||||
import { hasExplicitOptions } from "../command-options.js";
|
||||
@@ -171,7 +170,6 @@ export function registerMaintenanceCommands(program: Command) {
|
||||
sessionSqliteAllAgents: Boolean(opts.sessionSqliteAllAgents),
|
||||
sessionSqliteGithubIssue: Boolean(opts.githubIssue),
|
||||
json: Boolean(opts.json),
|
||||
crossStateDirImports: resolveDoctorCrossStateDirImports(),
|
||||
});
|
||||
defaultRuntime.exit(0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user