You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A chat-agent save_model call that only carries the field being added replaces the entire model definition, and auto-merge lands the truncated model on main without any breaking-change or content check. On Contentrain/iterum this wiped 38 of 39 fields from the home-page model while all of the content still used them.
What happened (Contentrain/iterum, 2026-08-21 08:23:45–08:24:03Z)
time
commit
actor
08:23:45
328ff18d Merge main into contentrain
contentrain-studio-dev[bot]
08:23:48
3b7b7c8d contentrain: save model home-page
Contentrain Studio <ai@contentrain.io>, Co-Authored-By oraysercan
08:23:51
46c48cf4 Merge cr/model/home-page/1787300627-7626 into contentrain
bot
08:24:00
44ad31a8 contentrain: regenerate context.json
Studio
08:24:03
080e4e58 Merge contentrain into main
bot
3b7b7c8d is -172/+2 on .contentrain/models/home-page.json. The parent had 39 fields plus title_field and description; the committed file is:
.contentrain/content/marketing/home-page/en.json was untouched and still carried 34 keys that no longer exist in the model. title_field (required by @contentrain/mcp's model_save schema) is missing. context.json records source: "mcp-studio", tool: "merge".
An earlier Studio save on the same model (8bcdada5, 2026-08-13, -57) shows the same pattern in a milder form: it dropped the pricing_preview object field and ~10 nested sub-fields that content still populated. So this is recurring, not a one-off.
Root cause (studio code)
Tool semantics are replace, but nothing says so.server/utils/agent-tools.ts — save_model is described as "Create or update a model definition"; fields is a bare object with no note that it must contain every field, and title_field is not in the input schema at all. An "add one image field" request naturally produces a payload containing only that field.
No merge with the existing definition.server/utils/conversation-engine.tscase 'save_model' passes the tool params straight to engine.saveModel(params as ModelDefinition, userEmail) — the existing model is never loaded.
Save-time validation looks at the new definition in isolation.server/utils/content-engine/save-model.ts calls validateModelDefinition(definition, config, existingModelIds). detectBreakingChanges() exists in schema-validation.ts but is only invoked from validateProjectSchema() (the audit path), and field_removed is severity: 'warning' regardless of how many entries still carry the field — so it would not block even if it ran. Existing content is never validated against the new model.
Auto-merge has no validation gate.mergeForTool → mergeToContentrain → finalize → merge into main runs with no validate step, so the truncated model reached main in 18 seconds (and closed iterum PR Chore/bump contentrain 1.8.0 #120 as merged by the bot).
(The REST models/[modelId].patch.ts route is not affected: it loads the existing model from the brain cache and merges — but it can only change title_field/form config, so it is not the path that adds fields.)
Expected
Saving a model should never silently drop fields that existing content still uses, and a destructive model change should not auto-merge to main without a gate.
Proposed fix
conversation-enginesave_model: load the current definition (brain cache) and merge fields into it; require an explicit remove_fields: [...] (or replace: true) for deletions.
saveModel(): run detectBreakingChanges(prev, next); treat field_removed/type change with affectedEntries > 0 as error (block), not warning. Validate existing entries against the new model before committing.
agent-tools.ts: document the full-definition/replace semantics in the tool description and add title_field to inputSchema (mirrors @contentrain/mcp).
Auto-merge: run validate on the feature branch before mergeToContentrain/finalize; refuse to auto-merge when the model is invalid for its content.
Recovery
iterum main was repaired in PR #121 (fd8bbd75) by restoring the full 41-field model; hero_background_image was dropped since no content or component referenced it.
Summary
A chat-agent
save_modelcall that only carries the field being added replaces the entire model definition, and auto-merge lands the truncated model onmainwithout any breaking-change or content check. OnContentrain/iterumthis wiped 38 of 39 fields from thehome-pagemodel while all of the content still used them.What happened (Contentrain/iterum, 2026-08-21 08:23:45–08:24:03Z)
328ff18dMerge main into contentrain3b7b7c8dcontentrain: save model home-page<ai@contentrain.io>, Co-Authored-By oraysercan46c48cf4Mergecr/model/home-page/1787300627-7626into contentrain44ad31a8contentrain: regenerate context.json080e4e58Merge contentrain into main3b7b7c8dis-172/+2on.contentrain/models/home-page.json. The parent had 39 fields plustitle_fieldanddescription; the committed file is:{ "id": "home-page", "name": "Home Page", "kind": "singleton", "domain": "marketing", "i18n": true, "fields": { "hero_background_image": { "description": "Background/banner image for the hero CTA section.", "type": "image" } } }.contentrain/content/marketing/home-page/en.jsonwas untouched and still carried 34 keys that no longer exist in the model.title_field(required by@contentrain/mcp'smodel_saveschema) is missing.context.jsonrecordssource: "mcp-studio", tool: "merge".An earlier Studio save on the same model (
8bcdada5, 2026-08-13,-57) shows the same pattern in a milder form: it dropped thepricing_previewobject field and ~10 nested sub-fields that content still populated. So this is recurring, not a one-off.Root cause (studio code)
server/utils/agent-tools.ts—save_modelis described as "Create or update a model definition";fieldsis a bare object with no note that it must contain every field, andtitle_fieldis not in the input schema at all. An "add one image field" request naturally produces a payload containing only that field.server/utils/conversation-engine.tscase 'save_model'passes the tool params straight toengine.saveModel(params as ModelDefinition, userEmail)— the existing model is never loaded.server/utils/content-engine/save-model.tscallsvalidateModelDefinition(definition, config, existingModelIds).detectBreakingChanges()exists inschema-validation.tsbut is only invoked fromvalidateProjectSchema()(the audit path), andfield_removedisseverity: 'warning'regardless of how many entries still carry the field — so it would not block even if it ran. Existing content is never validated against the new model.mergeForTool→mergeToContentrain→ finalize → merge intomainruns with novalidatestep, so the truncated model reachedmainin 18 seconds (and closed iterum PR Chore/bump contentrain 1.8.0 #120 as merged by the bot).(The REST
models/[modelId].patch.tsroute is not affected: it loads the existing model from the brain cache and merges — but it can only changetitle_field/form config, so it is not the path that adds fields.)Expected
Saving a model should never silently drop fields that existing content still uses, and a destructive model change should not auto-merge to
mainwithout a gate.Proposed fix
conversation-enginesave_model: load the current definition (brain cache) and mergefieldsinto it; require an explicitremove_fields: [...](orreplace: true) for deletions.saveModel(): rundetectBreakingChanges(prev, next); treatfield_removed/type change withaffectedEntries > 0aserror(block), notwarning. Validate existing entries against the new model before committing.agent-tools.ts: document the full-definition/replace semantics in the tool description and addtitle_fieldtoinputSchema(mirrors@contentrain/mcp).validateon the feature branch beforemergeToContentrain/finalize; refuse to auto-merge when the model is invalid for its content.Recovery
iterum
mainwas repaired in PR #121 (fd8bbd75) by restoring the full 41-field model;hero_background_imagewas dropped since no content or component referenced it.