fix: guard string component variable values - #480
Merged
Conversation
A component variable `default_value` is untyped in the MCP tool schema, so an
agent can store a bare string where the renderer expects
`{ type: 'dynamic_text', data: { content } }`. Probing that primitive with the
`in` operator threw, `fetchPageByPath` swallowed the error, and every page
using the component fell back to the starter template — both in the builder
preview and on the published site.
Coerce strings to `dynamic_text` at render time so existing bad data recovers
without a migration, and wrap them at the MCP boundary so new writes are
stored in the correct shape.
Co-authored-by: Cursor <cursoragent@cursor.com>
ysamcode
requested review from
liamwalder,
lunenas and
tristan-mouchet
as code owners
July 30, 2026 08:57
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A component variable's
default_valueis untyped in the MCP tool schema, so an agent can store a bare string ("Accessories") where the renderer expects{ type: 'dynamic_text', data: { content: 'Accessories' } }. Probing that primitive with theinoperator throwsTypeError: Cannot use 'in' operator to search for 'type' in Accessories,fetchPageByPathswallows the error and returns null, so every page using the component falls back to the starter template — in the builder preview and on the published site.Found on a live site whose header component held two such values: all 74 pages served the default "Welcome to Ycode" screen instead of their content.
This hardens the render path so existing bad data recovers without a migration, and closes the write path so new values are stored in the correct shape.
Changes
normalizeComponentVariableValueinlib/variable-utils.ts, coercing a bare string todynamic_textand rejecting other primitivesinprobe inapplyComponentOverrides(lib/resolve-components.ts) — the crash siteextractTiptapFromComponentVariable, which had the same unguarded proberesolveLayerAssets(lib/page-fetcher.ts) against a primitivedefault_values at the MCP boundary innormalizeVariables(lib/mcp/tools/components.ts) — Tiptap forrich_textvariables,dynamic_textotherwiseset_component_instancealready takes a typedtext: z.string()and builds the value itself, sodefault_valuewas the only unvalidated path.Test plan
create_componentwithdefault_value: "Some label"on a text variable, add an instance to a page, and confirm the page renders the label instead of falling back to the starter templaterich_textvariable and confirm the string is stored as Tiptap contentdefault_valueto a bare string directly in the database, then load a page using that component — should render the string, not 500{ type, data }defaults, per-instance overrides, and variant switching all render unchangedtypekey) are unaffectednpm run lintandnpm run type-checkpassMade with Cursor