Skip to content

Instantly share code, notes, and snippets.

@aaronschachter
Last active May 28, 2026 04:02
Show Gist options
  • Select an option

  • Save aaronschachter/a7e70b042d08912f17fc06f3af7f07ad to your computer and use it in GitHub Desktop.

Select an option

Save aaronschachter/a7e70b042d08912f17fc06f3af7f07ad to your computer and use it in GitHub Desktop.
CMSEXT-2030: SIP slug validation investigation

CMSEXT-2030: SIP Slug Validation Investigation

Item under investigation

  • Item ID: 696102c94181b4eaba29f101
  • Collection ID: 65e89895c5a4b8d764c0d895
  • Slug: learning-and-development-trends
  • Site ID: 65e89895c5a4b8d764c0d710
  • Last successful publish: 2026-05-12T08:10:55.491Z

Root cause

PR #109099 ("feat(custom-domains): add branch domain awareness to CustomDomainService") merged on May 19, expanding which publications are included in the allLive array during slug uniqueness validation. Before this PR, branch domain publications were not included. After it, they are.

Publication 69258a3f79dfa589c6408c34 (branch domain tts-widget-v2-0, created 2020, last updated July 2025) contains a different item (6841aa09d613692e33b5fd17) with the same slug learning-and-development-trends. This stale item was published to that branch domain before our log retention window and was previously invisible to slug validation.

Timeline

  • May 12: Item 696102c94181b4eaba29f101 published successfully to all publications (branch domains not checked)
  • May 19: PR #109099 merges, branch domain publications now included in allLive
  • May 21: First SIP failure, slug validation rejects self-update because stale item in branch domain publication is now visible

Mechanism

During SIP, validateItem at lines 274-300 builds the itemIdsBySlugAndLocale map from staging + ALL live publication databases in a single pass. With PR #109099, the old branch domain publication 69258a3f79dfa589c6408c34 is now included.

The map query finds:

  • 696102c94181b4eaba29f101 (from staging + 7 publication DBs)
  • 6841aa09d613692e33b5fd17 (from the stale branch domain publication)

The set for key learning-and-development-trends_<localeId> now contains 2 item IDs. The validation at textField.ts:135 checks existingItemIds.size > 1 and throws immediately, before it reaches the has(docId) check at line 137. This happens during the FIRST publication's validation (which validates against ALL live DBs), so no publication gets written to, even the 7 that have the correct item ID.

The check

// textField.ts:132-140
if (
  existingItemIds &&
  (existingItemIds.size > 1 ||           // <-- short-circuits here
    !existingItemIds.has(String(docId)))  // <-- never reached
) {
  throw self._makeError('Unique value is already in database', value);
}

The size > 1 check was introduced in PR #74887 (CMSAUTH-3411, Oct 2024) as a "robust slug uniqueness check." It's correct when all IDs in the set are from the same publication DB. But when the set aggregates across multiple publication DBs (as it does via allLive), a stale item in one publication poisons the check for all publications.

Possible fixes

Option A: Fix the slug uniqueness check in textField.ts

Remove the existingItemIds.size > 1 short-circuit and only check !existingItemIds.has(String(docId)):

// Before (current):
if (
  existingItemIds &&
  (existingItemIds.size > 1 ||
    !existingItemIds.has(String(docId)))
) {
  throw self._makeError('Unique value is already in database', value);
}

// After (proposed):
if (
  existingItemIds &&
  !existingItemIds.has(String(docId))
) {
  throw self._makeError('Unique value is already in database', value);
}

Rationale: If the current item's ID is in the set, it's a self-update and should be allowed regardless of how many other IDs exist. The size > 1 check was an optimization (fail fast) but is incorrect when the set contains IDs from multiple databases. Removing it doesn't weaken the validation: !has(docId) already catches the case where a different item owns the slug.

Edge cases: On insert, docId is undefined, so String(undefined) = "undefined" won't be in the set, and !has correctly rejects the duplicate. No behavior change for inserts.

Scope: Affects all slug uniqueness checks globally (SIP, CSV import, API).

Option B: Exclude branch domain publications from allLive during SIP

Revert the effect of PR #109099 specifically for the SIP validation path, so branch domain publications are not included in the allLive array when building the itemIdsBySlugAndLocale map. This restores the pre-May-19 behavior where branch domains were invisible to slug validation.

Rationale: Branch domain publications may contain stale data from old publishes. Including them in slug validation introduces false positives for sites with long-lived branch domains. The slug uniqueness check was working correctly before branch domains were added to allLive.

Scope: Only affects the SIP path, not CSV import or API. Branch domains would still receive writes via writeToAllLive but wouldn't contribute to the pre-validation slug map.

Customer unblock

The stale item in publication 69258a3f79dfa589c6408c34 needs to be resolved. Options:

  • Republish the branch domain branch--tts-widget-v2-0-synthesia-staging-eaff-03a8b1.webflow.io (full site publish to that target would reconcile the stale item)
  • Delete/unpublish the stale branch domain if it's no longer needed

Slug spot-check: item ID for learning-and-development-trends per environment

Environment Publication ID Item ID Matches staging? Admin Link
Staging N/A 696102c94181b4eaba29f101 -- admin
Pub (checked) 6a159632500787005df4ef69 696102c94181b4eaba29f101 YES admin
Pub (checked) 6a159d9b2f629a1884f72afd 696102c94181b4eaba29f101 YES admin
Pub (checked) 6a05893a12d665a1493ce14f 696102c94181b4eaba29f101 YES admin
Pub (checked) 69fb303c8a0016fc8c3dd4e8 696102c94181b4eaba29f101 YES admin
Pub (MISMATCH) 69258a3f79dfa589c6408c34 6841aa09d613692e33b5fd17 NO admin
Pub (checked) 69e241a56585e579e629fc76 696102c94181b4eaba29f101 YES admin
Pub (checked) 6a155e10b324501045b27b5c 696102c94181b4eaba29f101 YES admin
Pub (checked) 6a158da297cf03e049b19b09 696102c94181b4eaba29f101 YES admin
Pub (checked) 6a159cee33ff32bf93ab28b7 not found N/A admin

Publication 69258a3f79dfa589c6408c34 is the oldest (created 2020-05-27, last updated 2025-07-03) and has a different set of collections than the newer publications. The stale item 6841aa09d613692e33b5fd17 has zero logs in Datadog (even flex storage back to Jan 2026), so it predates the log retention window.

Datadog links

Key code paths

File What
entrypoints/server/routes/dynamo/v1/items.ts:848-908 PUT/PATCH /primary route handlers (fixed in PR #110150 to return 500)
entrypoints/server/lib/logic/cms/items/updateLocalizedCMSItemsByPrimary.ts:305-357 attemptLocalizedItemUpdate catches errors and returns as data
entrypoints/server/lib/logic/cms/internal/items/writeToAllLive.ts:88-276 Iterates publication DBs, per-pub validation
entrypoints/server/lib/logic/cms/items/updateCMSItems.ts:387-414 validateItem input, skipLiveValidation: true, skipUniqueValidation
entrypoints/server/lib/logic/cms/internal/items/validateItem.ts:274-300 Builds itemIdsBySlugAndLocale from staging + allLive
entrypoints/server/lib/dynamo/schemaValidator/fields/textField.ts:118-141 Slug uniqueness check (existingItemIds.size > 1 short-circuits before has(docId))

PRs

  • #109099: Branch domain awareness (introduced the regression by expanding allLive)
  • #74887: Robust slug uniqueness check (introduced existingItemIds.size > 1, Oct 2024)
  • #110150: Returns 500 when all locale updates fail + integration test reproducing the bug
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment