From bf8ca07deb704b7f50a1db792f88c93e7a4e15be Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 24 Feb 2026 23:02:45 +0000 Subject: [PATCH] fix(config): soften antigravity removal fallout (#25538) Land #25538 by @chilu18 to keep legacy google-antigravity-auth config entries non-fatal after removal (see #25862). Co-authored-by: chilu18 --- CHANGELOG.md | 1 + src/config/config.plugin-validation.test.ts | 42 +++++++++++++++++++++ src/config/validation.ts | 35 +++++++++-------- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb89d49e0e..a87de8b23ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Config/Plugins: treat stale removed `google-antigravity-auth` plugin references as compatibility warnings (not hard validation errors) across `plugins.entries`, `plugins.allow`, `plugins.deny`, and `plugins.slots.memory`, so startup no longer fails after antigravity removal. (#25538, #25862) Thanks @chilu18. - Security/Workspace FS: normalize `@`-prefixed paths before workspace-boundary checks (including workspace-only read/write/edit and sandbox mount path guards), preventing absolute-path escape attempts from bypassing guard validation. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Native images: enforce `tools.fs.workspaceOnly` for native prompt image auto-load (including history refs), preventing out-of-workspace sandbox mounts from being implicitly ingested as vision input. This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Exec approvals: bind `system.run` command display/approval text to full argv when shell-wrapper inline payloads carry positional argv values, and reject payload-only `rawCommand` mismatches for those wrapper-carrier forms, preventing hidden command execution under misleading approval text. This ships in the next npm release. Thanks @tdjackey for reporting. diff --git a/src/config/config.plugin-validation.test.ts b/src/config/config.plugin-validation.test.ts index b9fb08e4d8d..d9e6b3190e1 100644 --- a/src/config/config.plugin-validation.test.ts +++ b/src/config/config.plugin-validation.test.ts @@ -103,6 +103,48 @@ describe("config plugin validation", () => { } }); + it("warns for removed legacy plugin ids instead of failing validation", async () => { + const home = await createCaseHome(); + const removedId = "google-antigravity-auth"; + const res = validateInHome(home, { + agents: { list: [{ id: "pi" }] }, + plugins: { + enabled: false, + entries: { [removedId]: { enabled: true } }, + allow: [removedId], + deny: [removedId], + slots: { memory: removedId }, + }, + }); + expect(res.ok).toBe(true); + if (res.ok) { + expect(res.warnings).toEqual( + expect.arrayContaining([ + { + path: `plugins.entries.${removedId}`, + message: + "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", + }, + { + path: "plugins.allow", + message: + "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", + }, + { + path: "plugins.deny", + message: + "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", + }, + { + path: "plugins.slots.memory", + message: + "plugin removed: google-antigravity-auth (stale config entry ignored; remove it from plugins config)", + }, + ]), + ); + } + }); + it("surfaces plugin config diagnostics", async () => { const home = await createCaseHome(); const pluginDir = path.join(home, "bad-plugin"); diff --git a/src/config/validation.ts b/src/config/validation.ts index f2ee1867485..746f89ef0e4 100644 --- a/src/config/validation.ts +++ b/src/config/validation.ts @@ -22,6 +22,8 @@ import { findLegacyConfigIssues } from "./legacy.js"; import type { OpenClawConfig, ConfigValidationIssue } from "./types.js"; import { OpenClawSchema } from "./zod-schema.js"; +const LEGACY_REMOVED_PLUGIN_IDS = new Set(["google-antigravity-auth"]); + function isWorkspaceAvatarPath(value: string, workspaceDir: string): boolean { const workspaceRoot = path.resolve(workspaceDir); const resolved = path.resolve(workspaceRoot, value); @@ -313,6 +315,19 @@ function validateConfigObjectWithPluginsBase( } const { registry, knownIds, normalizedPlugins } = ensureRegistry(); + const pushMissingPluginIssue = (path: string, pluginId: string) => { + if (LEGACY_REMOVED_PLUGIN_IDS.has(pluginId)) { + warnings.push({ + path, + message: `plugin removed: ${pluginId} (stale config entry ignored; remove it from plugins config)`, + }); + return; + } + issues.push({ + path, + message: `plugin not found: ${pluginId}`, + }); + }; const pluginsConfig = config.plugins; @@ -320,10 +335,7 @@ function validateConfigObjectWithPluginsBase( if (entries && isRecord(entries)) { for (const pluginId of Object.keys(entries)) { if (!knownIds.has(pluginId)) { - issues.push({ - path: `plugins.entries.${pluginId}`, - message: `plugin not found: ${pluginId}`, - }); + pushMissingPluginIssue(`plugins.entries.${pluginId}`, pluginId); } } } @@ -334,10 +346,7 @@ function validateConfigObjectWithPluginsBase( continue; } if (!knownIds.has(pluginId)) { - issues.push({ - path: "plugins.allow", - message: `plugin not found: ${pluginId}`, - }); + pushMissingPluginIssue("plugins.allow", pluginId); } } @@ -347,19 +356,13 @@ function validateConfigObjectWithPluginsBase( continue; } if (!knownIds.has(pluginId)) { - issues.push({ - path: "plugins.deny", - message: `plugin not found: ${pluginId}`, - }); + pushMissingPluginIssue("plugins.deny", pluginId); } } const memorySlot = normalizedPlugins.slots.memory; if (typeof memorySlot === "string" && memorySlot.trim() && !knownIds.has(memorySlot)) { - issues.push({ - path: "plugins.slots.memory", - message: `plugin not found: ${memorySlot}`, - }); + pushMissingPluginIssue("plugins.slots.memory", memorySlot); } let selectedMemoryPluginId: string | null = null;