← Back to PRs

#10943: fix(config): resolve Control UI "Unsupported schema node" for config form sections

by kraftbj open 2026-02-07 06:22 View on GitHub →
stale
#### Summary The Control UI config form shows "Unsupported schema node. Use Raw mode." for several sections. The UI's schema walker can't handle three patterns in the generated JSON schema: `additionalProperties: true`, multi-branch `anyOf`/`oneOf` unions mixing primitives and objects, and empty `{}` leaf nodes (no `type` keyword). 7 paths affected: `channels`, `channels.telegram.capabilities`, `channels.telegram.customCommands`, `channels.telegram.accounts`, `agents.defaults.subagents.model`, `agents.defaults.sandbox.docker.ulimits`, `agents.list`. lobster-biscuit #### Repro Steps 1. Open the Control UI config form. 2. Navigate to channels, Telegram capabilities/customCommands/accounts, agent model, or Docker ulimits sections. 3. "Unsupported schema node. Use Raw mode." instead of editable form fields. #### Root Cause Five schema patterns the UI walker can't render, each introduced independently: | Issue | Introducing Commit | Date | |---|---|---| | `additionalProperties: true` on channels object | `1ad26d6fe` Config: schema-driven channels and settings | 2026-01-16 | | `AgentModelSchema` mixed `anyOf` union | `007f8c922` Config: support per-agent model fallbacks | 2026-01-09 | | `TelegramCustomCommandSchema` `.transform()` producing `{}` | `929666a8c` fix: add telegram custom commands (#860) | 2026-01-16 | | `TelegramCapabilitiesSchema` mixed `anyOf` union | `69761e8a5` feat: scope telegram inline buttons | 2026-01-16 | | Docker `ulimits` mixed `anyOf` union | `4f58e6aa7` feat(sandbox): per-agent docker overrides | 2026-01-08 | None of these were bugs at the time—they only became UI issues when the Control UI's schema-driven config form was built and the walker encountered patterns it doesn't support. The root cause is a mismatch between what the Zod-to-JSON-Schema pipeline produces and what the UI can render. Specific issues: 1. **`additionalProperties: true` on channels** — `stripChannelSchema()` set this after clearing channel properties, but `applyChannelSchemas()` already merges channel schemas explicitly. The blanket `true` was unnecessary and breaks the UI walker. 2. **Empty `{}` leaf nodes** — `TelegramCustomCommandSchema` used `.transform()` directly on `z.string()`. Zod v4's `toJSONSchema({unrepresentable: "any"})` can't represent the input type when only a transform is present, so it emits `{}`. 3. **Mixed `anyOf` unions** — `AgentModelSchema` (`z.union([z.string(), z.object(...)])`), `TelegramCapabilitiesSchema` (`z.union([z.array(), z.object()])`), and Docker `ulimits` (`z.union([z.string(), z.number(), z.object()])`) all produce `anyOf` with both primitive and complex branches. #### Behavior Changes - JSON schema output shape changes (no empty `{}` leaves, no mixed unions, `channels.additionalProperties` is `false`). Runtime validation behavior is preserved. - `AgentModelSchema` now normalizes string shorthand (`"openai/gpt-4o"`) to `{primary: "openai/gpt-4o"}` during Zod parsing via `z.preprocess()`. Config files with the string form still work—preprocess handles the conversion. - No user-facing behavior changes beyond the Control UI now rendering these sections. #### Codebase and GitHub Search - Searched all `additionalProperties` usage in schema generation. - Searched all `.transform()` in Zod schemas that feed into JSON schema output. - Searched all `z.union` patterns mixing string/object branches. - Verified `formatUlimitValue`, `typeof modelCfg === "string"` checks, and `normalizeCapabilities` still work correctly after schema changes. #### Changes | File | Change | |---|---| | `src/config/schema.ts` | Changed `additionalProperties` to `false` in `stripChannelSchema()`; added `patchSchemaForUI()` recursive post-processor that replaces empty `{}` leaves with `{type: "string"}` and simplifies mixed `anyOf`/`oneOf` unions by picking the most descriptive branch (object > array > primitive). Runs on both `buildBaseConfigSchema()` and `buildConfigSchema()` to handle channel/plugin merge timing. | | `src/config/zod-schema.providers-core.ts` | Switched `TelegramCustomCommandSchema` fields from `.transform()` to `.pipe()` to preserve input schema in JSON output. | | `src/config/zod-schema.agent-runtime.ts` | Replaced `AgentModelSchema` union with `z.preprocess()` that normalizes string shorthand; deduplicated `subagents.model` inline union to reuse `AgentModelSchema`. | | `src/config/schema.test.ts` | Added 5 regression tests for schema fidelity. | #### Tests - `pnpm build` — passes - `pnpm check` — passes (lint clean) - `pnpm test` — all 840 test files / 5355 tests pass (only pre-existing lancedb extension failures) - Added regression tests in `src/config/schema.test.ts`: - `channels.additionalProperties` is not `true` - No empty `{}` leaf nodes in generated schema - `AgentModelSchema` produces an object schema (no anyOf/oneOf) - No mixed complex/primitive anyOf unions - Custom command fields aren't empty objects after patching #### Manual Testing ##### Prerequisites - Control UI running against a dev gateway ##### Steps 1. Open the Control UI config form. 2. Navigate to channels, Telegram, agent model, and Docker ulimits sections. 3. Verify all sections render editable form fields instead of "Unsupported schema node" messages. **Sign-Off** - Models used: Claude Opus 4.6 - Submitter effort: investigated schema walker requirements, traced root causes across Zod-to-JSON-schema pipeline, identified introducing commits via git log <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> - Updates config JSON schema generation to be compatible with Control UI’s schema walker by post-processing the generated Draft-07 schema (patching empty `{}` leaves and simplifying mixed `anyOf`/`oneOf` unions). - Changes `channels` schema stripping to set `additionalProperties: false` rather than `true`. - Adjusts Zod schemas to avoid “unrepresentable” `{}` output (`.transform()` → `.pipe(...)`) and normalizes agent model shorthand using `z.preprocess`. - Adds regression tests to ensure the generated schema avoids the unsupported patterns. <h3>Confidence Score: 3/5</h3> - This PR is close to safe to merge but has a couple schema-generation correctness risks that should be addressed. - The changes are localized to schema generation and tests, but the new post-processor mutates cached schema objects in-place and the union-simplification merge order can overwrite/strip schema metadata or constraints, potentially changing the exported schema beyond the intended UI compatibility fixes. - src/config/schema.ts <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs