TL;DR
When a downstream patch adds a field to an extension's extensions/<channel>/src/config-schema.ts zod schema, the field is correctly compiled into dist/extensions/<channel>/config-schema-*.js but is NOT picked up by the runtime config validator. The validator gates channels.<channel>.<key> against src/config/bundled-channel-config-metadata.generated.ts — a checked-in codegen output that is not regenerated as part of npm run build. Without an entry in that file, the gateway rejects the new field at startup with:
channels.<channel>: invalid config: must NOT have additional properties
Run "openclaw doctor --fix" to repair, then retry.
Gateway failed to start: Error: Invalid config at /Users/<u>/.openclaw/openclaw.json
Repro (minimal)
- Patch
extensions/bluebubbles/src/config-schema.ts to add an optional zod field, e.g.:
foo: z.string().optional(),
- Run
npm run build (full pnpm install && npm run build chain).
- Confirm the field appears in
dist/extensions/bluebubbles/config-schema-*.js (it does — the per-extension bundle compiles correctly).
- Confirm the field does NOT appear in
src/config/bundled-channel-config-metadata.generated.ts (it doesn't — the codegen wasn't re-run).
- Add
channels.bluebubbles.foo: "bar" to ~/.openclaw/openclaw.json.
- Start the gateway. It crashes with
channels.bluebubbles: invalid config: must NOT have additional properties.
Why it happens
src/config/bundled-channel-config-metadata.generated.ts carries the .generated.ts suffix and is bundled into dist/io-*.js (and consumed by src/channels/plugins/config-schema.ts via safeParseJsonSchema(schema, options?.cacheKey ?? "channel-config-schema:json", value)). The file IS marked generated, but it's checked into source control and not re-run from the per-extension zod schemas during npm run build. So zod source changes that don't include a hand-edit (or codegen run) of this bundled file silently ship a half-broken state: per-extension bundle has the field, runtime validator does not.
Suggested fix (one of)
A. Best: hook the codegen step into npm run build (e.g., as a prebuild script) so the bundled file regenerates from the per-extension zod sources on every build. Removes the trap entirely for downstream patches and refactors.
B. Cheapest: add an npm run build post-step that asserts every property in the per-extension config-schema.ts zod schemas is present in the bundled JSON schema for that channel. Fails the build with a clear message pointing at the missing keys.
C. Workaround for downstream patches (current state): every patch that touches extensions/<channel>/src/config-schema.ts must mirror the additions in src/config/bundled-channel-config-metadata.generated.ts at both the channel-level and the per-account level. Brittle and easy to miss — we hit this exactly in our downstream and reproduced the diagnostic chain documented in https://github.com/markthebest12/openclaw-infra/issues/1258 (filed today after our gateways crashlooped on activation of a new BlueBubbles peerAgents field).
Real-world impact
We added three optional fields (peerAgents: string[], peerAgentsRequireMention: boolean, mentionName: string) to extensions/bluebubbles/src/config-schema.ts via a downstream nix-flake overlay (openclaw-infra#1258). All upstream-visible CI/build steps passed. Activation in production crashlooped both of our gateways with the additional-properties error. We had to ship a wrapper patch (https://github.com/markthebest12/openclaw-infra/pull/1464) that mirrors the additions into src/config/bundled-channel-config-metadata.generated.ts at both the channel-level (schema.properties) and the per-account level (schema.properties.accounts.items.properties).
The bb-inbound-humanizing.patch upstream-equivalent in our overlay already does this correctly (it modifies the bundled file when adding debugWebhookPayloads). Without that prior pattern as a reference, we'd have spent much longer diagnosing the asymmetry between zod source and the runtime validator.
Environment
- openclaw v2026.5.6 (per nix-openclaw rev
44ae6995de50)
- Node.js v22.22.2
- macOS 26.4.1
- Per-user gateway config at
/Users/<user>/.openclaw/openclaw.json
Proposed acceptance for B (build-time guard)
// In a build-time check (or a generator script)
import { ChannelConfigSchemaRegistry } from "./extensions/.../config-schema";
import { BUNDLED_CHANNEL_CONFIG_METADATA } from "./src/config/bundled-channel-config-metadata.generated";
for (const channel of BUNDLED_CHANNEL_CONFIG_METADATA) {
const zodKeys = new Set(Object.keys(getZodObjectShape(ChannelConfigSchemaRegistry[channel.pluginId])));
const jsonKeys = new Set(Object.keys(channel.schema.properties));
const missing = [...zodKeys].filter(k => !jsonKeys.has(k));
if (missing.length > 0) {
throw new Error(`bundled-channel-config-metadata.generated.ts missing keys for ${channel.pluginId}: ${missing.join(', ')} — re-run codegen`);
}
}
Happy to send a PR if helpful — let us know which approach you'd prefer.
TL;DR
When a downstream patch adds a field to an extension's
extensions/<channel>/src/config-schema.tszod schema, the field is correctly compiled intodist/extensions/<channel>/config-schema-*.jsbut is NOT picked up by the runtime config validator. The validator gateschannels.<channel>.<key>againstsrc/config/bundled-channel-config-metadata.generated.ts— a checked-in codegen output that is not regenerated as part ofnpm run build. Without an entry in that file, the gateway rejects the new field at startup with:Repro (minimal)
extensions/bluebubbles/src/config-schema.tsto add an optional zod field, e.g.:npm run build(fullpnpm install && npm run buildchain).dist/extensions/bluebubbles/config-schema-*.js(it does — the per-extension bundle compiles correctly).src/config/bundled-channel-config-metadata.generated.ts(it doesn't — the codegen wasn't re-run).channels.bluebubbles.foo: "bar"to~/.openclaw/openclaw.json.channels.bluebubbles: invalid config: must NOT have additional properties.Why it happens
src/config/bundled-channel-config-metadata.generated.tscarries the.generated.tssuffix and is bundled intodist/io-*.js(and consumed bysrc/channels/plugins/config-schema.tsviasafeParseJsonSchema(schema, options?.cacheKey ?? "channel-config-schema:json", value)). The file IS marked generated, but it's checked into source control and not re-run from the per-extension zod schemas duringnpm run build. So zod source changes that don't include a hand-edit (or codegen run) of this bundled file silently ship a half-broken state: per-extension bundle has the field, runtime validator does not.Suggested fix (one of)
A. Best: hook the codegen step into
npm run build(e.g., as aprebuildscript) so the bundled file regenerates from the per-extension zod sources on every build. Removes the trap entirely for downstream patches and refactors.B. Cheapest: add an
npm run buildpost-step that asserts every property in the per-extensionconfig-schema.tszod schemas is present in the bundled JSON schema for that channel. Fails the build with a clear message pointing at the missing keys.C. Workaround for downstream patches (current state): every patch that touches
extensions/<channel>/src/config-schema.tsmust mirror the additions insrc/config/bundled-channel-config-metadata.generated.tsat both the channel-level and the per-account level. Brittle and easy to miss — we hit this exactly in our downstream and reproduced the diagnostic chain documented in https://github.com/markthebest12/openclaw-infra/issues/1258 (filed today after our gateways crashlooped on activation of a new BlueBubblespeerAgentsfield).Real-world impact
We added three optional fields (
peerAgents: string[],peerAgentsRequireMention: boolean,mentionName: string) toextensions/bluebubbles/src/config-schema.tsvia a downstream nix-flake overlay (openclaw-infra#1258). All upstream-visible CI/build steps passed. Activation in production crashlooped both of our gateways with the additional-properties error. We had to ship a wrapper patch (https://github.com/markthebest12/openclaw-infra/pull/1464) that mirrors the additions intosrc/config/bundled-channel-config-metadata.generated.tsat both the channel-level (schema.properties) and the per-account level (schema.properties.accounts.items.properties).The
bb-inbound-humanizing.patchupstream-equivalent in our overlay already does this correctly (it modifies the bundled file when addingdebugWebhookPayloads). Without that prior pattern as a reference, we'd have spent much longer diagnosing the asymmetry between zod source and the runtime validator.Environment
44ae6995de50)/Users/<user>/.openclaw/openclaw.jsonProposed acceptance for B (build-time guard)
Happy to send a PR if helpful — let us know which approach you'd prefer.