Skip to content

Chat-agent save_model replaces the whole model definition, so adding one field wipes every other field and auto-merges to main #212

Description

@ABB65

Summary

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:

{
  "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.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)

  1. Tool semantics are replace, but nothing says so. server/utils/agent-tools.tssave_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.
  2. No merge with the existing definition. server/utils/conversation-engine.ts case 'save_model' passes the tool params straight to engine.saveModel(params as ModelDefinition, userEmail) — the existing model is never loaded.
  3. 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.
  4. Auto-merge has no validation gate. mergeForToolmergeToContentrain → 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-engine save_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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions