mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-28 11:31:13 +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);
|
||||
});
|
||||
|
||||
@@ -7467,7 +7467,6 @@ describe("update-cli", () => {
|
||||
nonInteractive: true,
|
||||
repair: true,
|
||||
yes: true,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
expect(syncPluginCall()?.channel).toBe("stable");
|
||||
expect(syncPluginCall()?.acknowledgeClawHubRisk).toBe(true);
|
||||
@@ -7568,7 +7567,6 @@ describe("update-cli", () => {
|
||||
nonInteractive: true,
|
||||
repair: true,
|
||||
yes: false,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
expect(syncPluginCall()?.channel).toBe("beta");
|
||||
expect(syncPluginCall()?.config).toEqual({
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from "node:path";
|
||||
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV } from "../../commands/doctor-invocation.js";
|
||||
import { doctorCommand } from "../../commands/doctor.js";
|
||||
import {
|
||||
UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR_ENV,
|
||||
@@ -231,7 +230,6 @@ export async function updateFinalizeCommand(opts: UpdateFinalizeOptions): Promis
|
||||
nonInteractive: true,
|
||||
repair: true,
|
||||
yes: opts.yes === true,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
configSnapshot = await readConfigFileSnapshot({ skipPluginValidation: true });
|
||||
if (requestedChannel) {
|
||||
@@ -520,7 +518,6 @@ export async function continuePostCoreUpdateInFreshProcess(params: {
|
||||
env: {
|
||||
...stripGatewayServiceMarkerEnv(disableUpdatedPackageCompileCacheEnv(process.env)),
|
||||
OPENCLAW_UPDATE_IN_PROGRESS: "1",
|
||||
[DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV]: "1",
|
||||
[POST_CORE_UPDATE_ENV]: "1",
|
||||
[POST_CORE_UPDATE_CHANNEL_ENV]: params.channel,
|
||||
...(params.requestedChannel
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
checkShellCompletionStatus,
|
||||
ensureCompletionCacheExists,
|
||||
} from "../../commands/doctor-completion.js";
|
||||
import { DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV } from "../../commands/doctor-invocation.js";
|
||||
import { doctorCommand } from "../../commands/doctor.js";
|
||||
import { UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV } from "../../commands/doctor/shared/update-phase.js";
|
||||
import { resolveGatewayPort } from "../../config/config.js";
|
||||
@@ -880,7 +879,6 @@ export function resolvePostInstallDoctorEnv(params?: {
|
||||
}): NodeJS.ProcessEnv {
|
||||
const resolvedEnv: NodeJS.ProcessEnv = {
|
||||
...disableUpdatedPackageCompileCacheEnv(params?.baseEnv ?? process.env),
|
||||
[DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV]: "1",
|
||||
};
|
||||
if (!params?.serviceEnv) {
|
||||
return resolvedEnv;
|
||||
@@ -1436,7 +1434,6 @@ export async function maybeRestartService(params: {
|
||||
process.stdin.isTTY && !params.opts.json && params.opts.yes !== true;
|
||||
await doctorCommand(defaultRuntime, {
|
||||
nonInteractive: !interactiveDoctor,
|
||||
crossStateDirImports: false,
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
|
||||
|
||||
@@ -3,7 +3,6 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV } from "../../commands/doctor-invocation.js";
|
||||
import { resolveGatewayInstallEntrypoint } from "../../daemon/gateway-entrypoint.js";
|
||||
import type { UpdateRunResult } from "../../infra/update-runner.js";
|
||||
import { updatePluginsAfterCoreUpdate } from "./update-command-plugins.js";
|
||||
@@ -208,7 +207,6 @@ describe("resolvePostInstallDoctorEnv", () => {
|
||||
|
||||
expect(env.PATH).toBe("/bin");
|
||||
expect(env.NODE_DISABLE_COMPILE_CACHE).toBe("1");
|
||||
expect(env[DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV]).toBe("1");
|
||||
expect(env.OPENCLAW_STATE_DIR).toBe(path.join("/srv/openclaw", "daemon-state"));
|
||||
expect(env.OPENCLAW_CONFIG_PATH).toBe(
|
||||
path.join("/srv/openclaw", "daemon-state", "openclaw.json"),
|
||||
@@ -227,7 +225,6 @@ describe("resolvePostInstallDoctorEnv", () => {
|
||||
|
||||
expect(env.PATH).toBe("/bin");
|
||||
expect(env.NODE_DISABLE_COMPILE_CACHE).toBe("1");
|
||||
expect(env[DOCTOR_DISABLE_CROSS_STATE_DIR_IMPORTS_ENV]).toBe("1");
|
||||
expect(env.OPENCLAW_STATE_DIR).toBe("/caller/state");
|
||||
expect(env.OPENCLAW_PROFILE).toBe("caller");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user