mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-17 22:51:35 +00:00
fix(doctor): gate cross-state-dir legacy imports behind explicit doctor opt-in (#103247)
* fix(doctor): gate cross-state-dir legacy imports behind explicit doctor opt-in A gateway or CLI command started with OPENCLAW_STATE_DIR pointing at a non-default directory used to import legacy files FROM the default ~/.openclaw state dir (exec-approvals.json, plugin-binding-approvals.json) and archive the originals with a .migrated suffix. Any isolated, test, or staging run on a host with production state silently captured and archived the production files. Cross-state-dir imports now run only with the new crossStateDirImports opt-in: openclaw doctor --fix (or an interactive doctor confirm) performs the import; the implicit CLI/gateway preflight leaves the default dir untouched and emits a notice pointing at doctor instead. * test(doctor): include notices in legacy-state detector mocks
This commit is contained in:
committed by
GitHub
parent
0fa49ad4dd
commit
2b3dc3042f
@@ -314,7 +314,9 @@ describe("ensureConfigReady", () => {
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("runs doctor flow before agent commands when default exec approvals must move to a custom state dir", async () => {
|
||||
it("does not run doctor flow for default-state-dir exec approvals when a custom state dir is set", async () => {
|
||||
// Cross-state-dir imports are doctor-owned; the implicit preflight must not
|
||||
// trigger (and must never archive) files that belong to the default dir.
|
||||
const root = useTempOpenClawHome();
|
||||
const stateDir = path.join(root, "custom-state");
|
||||
setTestEnvValue("OPENCLAW_STATE_DIR", stateDir);
|
||||
@@ -322,12 +324,7 @@ describe("ensureConfigReady", () => {
|
||||
|
||||
await runEnsureConfigReady(["agent"]);
|
||||
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledOnce();
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).toHaveBeenCalledWith({
|
||||
migrateState: true,
|
||||
migrateLegacyConfig: false,
|
||||
invalidConfigNote: false,
|
||||
});
|
||||
expect(loadAndMaybeMigrateDoctorConfigMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -98,20 +98,6 @@ function hasBundledChannelLegacyStateMigrationInputs(stateDir: string, oauthDir:
|
||||
return dirHasFile(oauthDir, isLegacyWhatsAppAuthFile);
|
||||
}
|
||||
|
||||
function hasLegacyExecApprovalsMigrationInput(stateDir: string): boolean {
|
||||
if (!process.env.OPENCLAW_STATE_DIR?.trim()) {
|
||||
return false;
|
||||
}
|
||||
const homeDir = resolveRequiredHomeDir(process.env, os.homedir);
|
||||
const sourcePath = path.join(homeDir, ".openclaw", "exec-approvals.json");
|
||||
const targetPath = path.join(stateDir, "exec-approvals.json");
|
||||
return (
|
||||
path.resolve(sourcePath) !== path.resolve(targetPath) &&
|
||||
fileOrDirExists(sourcePath) &&
|
||||
!fileOrDirExists(targetPath)
|
||||
);
|
||||
}
|
||||
|
||||
function hasPendingSqliteSidecarArchive(sourcePath: string): boolean {
|
||||
return (
|
||||
fileOrDirExists(`${sourcePath}.migrated`) &&
|
||||
@@ -147,8 +133,7 @@ function hasLegacyStateMigrationInputs(): boolean {
|
||||
sqliteSidecarPaths.some(
|
||||
(sourcePath) => fileOrDirExists(sourcePath) || hasPendingSqliteSidecarArchive(sourcePath),
|
||||
) ||
|
||||
hasBundledChannelLegacyStateMigrationInputs(stateDir, oauthDir) ||
|
||||
hasLegacyExecApprovalsMigrationInput(stateDir)
|
||||
hasBundledChannelLegacyStateMigrationInputs(stateDir, oauthDir)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user