Skip to content

fix(PROD-2211): robust handling of model "Unable to save the model" failures#167

Merged
jules-exel merged 1 commit into
mainfrom
fix/PROD-2211-model-save-handling
Jun 17, 2026
Merged

fix(PROD-2211): robust handling of model "Unable to save the model" failures#167
jules-exel merged 1 commit into
mainfrom
fix/PROD-2211-model-save-handling

Conversation

@jules-exel

Copy link
Copy Markdown
Contributor

PROD-2211

The model pusher (src/lib/pushers/model-pusher.ts) mishandled saveModel failures: it left incomplete mappings, silently dropped models, and reported success when models actually failed. The Unable to save the model. string comes from @agility/management-sdk (which just rethrows any failure generically) — the disambiguation has to happen in the CLI, which is where all of this lands. Neither SDK needed changes.

Fixes

1. Verify-then-recover on a failed save (creates and updates).
saveModel can reject even though the API persisted the model (timeout-with-server-completion, response-parse race). The catch now re-queries the target by (referenceName, contentDefinitionTypeID):

  • Create → recovered if a matching model now exists.
  • Update → recovered only if the saved field count matches the source, so a genuine reject (e.g. a 404 Definition for setting X not found, which leaves fields unapplied) correctly stays failed.

Recovered saves write the mapping and report success (silently).

2. Report failures honestly.
The update loop used if (result) where result is the string "updated" | "failed""failed" is truthy, so failed field-updates were counted as successes and the banner went green. Now compares === "updated", so failures increment failed, surface in the ERROR SUMMARY, and flip the banner.

3. Map-on-adopt.
When a model exists on the target by reference but has no mapping row, the non-default branch previously set targetModel and fell through every condition (all gated on sourceMapping), silently dropping the model. It now writes the mapping and skips, so re-syncs self-heal instead of wedging.

4. Type-aware lookups.
Model lookups are keyed by (referenceName, contentDefinitionTypeID) with a name-only fallback (older pulls/fixtures), closing the same-name different-type collision (e.g. a Content List vs a Module both named PromoBanner) — the PROD-1505 family.

Bonus: logger.model.error now unwraps the SDK's innerError, so the real API status + response body land in the log file instead of the generic Unable to save the model. (replaces a raw console.error debug block).

Tests

6 new cases in model-pusher.test.ts: failed-update-stays-failed, false-negative create recovery, different-type-same-name stays failed, failed-update recovery on field-count match, map-on-adopt writes the mapping + skips, and type-blind non-adoption. Full suite 1711 passing, typecheck clean.

Related

🤖 Generated with Claude Code

…ailures

The model pusher mishandled saveModel failures, leaving incomplete mappings and
reporting success when models actually failed. Four fixes:

1. Verify-then-map on a failed save (create AND update). saveModel can reject even
   though the API persisted the model. The catch now re-queries the target by
   (referenceName, contentDefinitionTypeID); a create is recovered if the model
   exists, an update if the saved field count matches the source (so a genuine
   reject like a 404 missing-definition still fails). Recovered saves write the
   mapping and report success.
2. Report failures honestly. The update loop used `if (result)` where result is the
   string "updated" | "failed" — "failed" is truthy, so failures were counted as
   successes with a green banner. Now compares `=== "updated"`.
3. Map-on-adopt. When a model exists on target by reference but has no mapping, the
   non-default branch now writes the mapping (and skips) instead of silently
   dropping the model, so re-syncs self-heal.
4. Type-aware lookups, keyed by (referenceName, contentDefinitionTypeID) with a
   name-only fallback, closing the same-name different-type collision (PROD-1505).

Also unwrap the SDK's `innerError` in logger.model.error so the real API status and
response body land in the log instead of the generic "Unable to save the model."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jules-exel jules-exel merged commit ff0104d into main Jun 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants