From 13aface863f1e196eb4e1975c8810f18d98ba4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E7=8C=AB=E5=AD=90?= <1811866786@qq.com> Date: Sat, 14 Feb 2026 10:07:12 +0800 Subject: [PATCH] fix(config): accept $schema key in root config (#15280) * fix(config): accept $schema key in root config (#14998) * fix: strip $schema via preprocess to avoid spurious UI section * fix(config): allow root without zod preprocess wrapper --------- Co-authored-by: Peter Steinberger --- CHANGELOG.md | 1 + docs/gateway/configuration.md | 2 +- docs/refactor/strict-config.md | 4 ++-- src/config/config.schema-key.test.ts | 24 ++++++++++++++++++++++++ src/config/schema.test.ts | 1 + src/config/schema.ts | 6 ++++++ src/config/zod-schema.ts | 1 + 7 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/config/config.schema-key.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbd1517745..b6a0ae6d0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ Docs: https://docs.openclaw.ai - Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293. - Status/Sessions: stop clamping derived `totalTokens` to context-window size, keep prompt-token snapshots wired through session accounting, and surface context usage as unknown when fresh snapshot data is missing to avoid false 100% reports. (#15114) Thanks @echoVic. - Providers/MiniMax: switch implicit MiniMax API-key provider from `openai-completions` to `anthropic-messages` with the correct Anthropic-compatible base URL, fixing `invalid role: developer (2013)` errors on MiniMax M2.5. (#15275) Thanks @lailoo. +- Config: accept `$schema` key in config file so JSON Schema editor tooling works without validation errors. (#14998) - Routing: enforce strict binding-scope matching across peer/guild/team/roles so peer-scoped Discord/Slack bindings no longer match unrelated guild/team contexts or fallback tiers. (#15274) Thanks @lailoo. - Web UI: add `img` to DOMPurify allowed tags and `src`/`alt` to allowed attributes so markdown images render in webchat instead of being stripped. (#15437) Thanks @lailoo. - Ollama/Agents: use resolved model/provider base URLs for native `/api/chat` streaming (including aliased providers), normalize `/v1` endpoints, and forward abort + `maxTokens` stream options for reliable cancellation and token caps. (#11853) Thanks @BrokenFinger98. diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 09c8f6c2968..46ba7af67b9 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -61,7 +61,7 @@ See the [full reference](/gateway/configuration-reference) for every available f ## Strict validation -OpenClaw only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to **refuse to start**. +OpenClaw only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to **refuse to start**. The only root-level exception is `$schema` (string), so editors can attach JSON Schema metadata. When validation fails: diff --git a/docs/refactor/strict-config.md b/docs/refactor/strict-config.md index 0c1d91c48ad..9605730c2b0 100644 --- a/docs/refactor/strict-config.md +++ b/docs/refactor/strict-config.md @@ -11,7 +11,7 @@ title: "Strict Config Validation" ## Goals -- **Reject unknown config keys everywhere** (root + nested). +- **Reject unknown config keys everywhere** (root + nested), except root `$schema` metadata. - **Reject plugin config without a schema**; don’t load that plugin. - **Remove legacy auto-migration on load**; migrations run via doctor only. - **Auto-run doctor (dry-run) on startup**; if invalid, block non-diagnostic commands. @@ -24,7 +24,7 @@ title: "Strict Config Validation" ## Strict validation rules - Config must match the schema exactly at every level. -- Unknown keys are validation errors (no passthrough at root or nested). +- Unknown keys are validation errors (no passthrough at root or nested), except root `$schema` when it is a string. - `plugins.entries..config` must be validated by the plugin’s schema. - If a plugin lacks a schema, **reject plugin load** and surface a clear error. - Unknown `channels.` keys are errors unless a plugin manifest declares the channel id. diff --git a/src/config/config.schema-key.test.ts b/src/config/config.schema-key.test.ts new file mode 100644 index 00000000000..effa08347fa --- /dev/null +++ b/src/config/config.schema-key.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vitest"; +import { OpenClawSchema } from "./zod-schema.js"; + +describe("$schema key in config (#14998)", () => { + it("accepts config with $schema string", () => { + const result = OpenClawSchema.safeParse({ + $schema: "https://openclaw.ai/config.json", + }); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.$schema).toBe("https://openclaw.ai/config.json"); + } + }); + + it("accepts config without $schema", () => { + const result = OpenClawSchema.safeParse({}); + expect(result.success).toBe(true); + }); + + it("rejects non-string $schema", () => { + const result = OpenClawSchema.safeParse({ $schema: 123 }); + expect(result.success).toBe(false); + }); +}); diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index e59eb2a9a74..98a6065cb31 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -7,6 +7,7 @@ describe("config schema", () => { const schema = res.schema as { properties?: Record }; expect(schema.properties?.gateway).toBeTruthy(); expect(schema.properties?.agents).toBeTruthy(); + expect(schema.properties?.$schema).toBeUndefined(); expect(res.uiHints.gateway?.label).toBe("Gateway"); expect(res.uiHints["gateway.auth.token"]?.sensitive).toBe(true); expect(res.version).toBeTruthy(); diff --git a/src/config/schema.ts b/src/config/schema.ts index 8af49bce47d..f3ae6bf2fa0 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -303,6 +303,12 @@ function stripChannelSchema(schema: ConfigSchema): ConfigSchema { if (!root || !root.properties) { return next; } + // Allow `$schema` in config files for editor tooling, but hide it from the + // Control UI form schema so it does not show up as a configurable section. + delete root.properties.$schema; + if (Array.isArray(root.required)) { + root.required = root.required.filter((key) => key !== "$schema"); + } const channelsNode = asSchemaObject(root.properties.channels); if (channelsNode) { channelsNode.properties = {}; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index d5289c34cb5..517ec16de24 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -95,6 +95,7 @@ const MemorySchema = z export const OpenClawSchema = z .object({ + $schema: z.string().optional(), meta: z .object({ lastTouchedVersion: z.string().optional(),