3.0.0's domain_config_update_10001() dispatches to \Drupal\domain_config\Service\DomainConfigMigration::migrateDomainConfiguration(). The legacy-name parser uses this regex:
$pattern = '/^domain\.config\.' . $domain_id . '(?:\.([a-z]{2}))?\.([^.]+\.[^.]+)$/';Two narrow captures silently drop legacy 2.x rows:
| base segments | langcode | matches? |
|---|---|---|
| 2 | none | yes |
| 2 | 2-letter | yes |
| 2 | 3-letter (fil) or hyphenated (pt-br, zh-hans, nb-no) |
no |
| 3+ | any | no |
Skipped rows are not deleted by cleanupLegacyConfigurations() (it only deletes rows that matched the regex), so the data is still on disk in the unpurged 2.x default storage — just invisible to the 3.x runtime. The matching gap also affects domain_config_ui.settings.overridable_configurations, which the migration writes to inside the loop: it picks up only the rows that matched.
Most visible casualty: system.theme.global (3-segment base name, served by SystemThemeSettingsForm extends ConfigFormBase which exposed the domain_config_ui toggle in 2.x). Sites using uncommon langcodes (pt-br, etc.) are also affected.
Proposed fix (3.0.x)
Patch DomainConfigMigration::migrateDomainConfiguration() to use the same prefix-strip + installed-langcode-list approach MR !379 introduced for 3.x's DomainConfigOverrideMigration:
- strip the
domain.config.{domain_id}.prefix, then split the payload on the first dot; - treat the first segment as a langcode iff it matches BCP47 shape AND is in the installed-languages list — otherwise treat it as part of the config name;
- everything else is the config name (3+ segments are fine);
- guard each
$collection->write($name, $data)with$collection->exists($name)to avoid stomping a value the admin re-set through the UI in the meantime; - track conflicts in the result array and surface them in the update message + watchdog.
No new _update_10002. The patch is silent; new 2.x → 3.0.1+ migrations work correctly out of the box on first run.
Recovery for existing 3.0.0 sites
Sites already at schema = 10001 with stranded legacy rows have two paths:
- Re-run
_update_10001by resetting the schema:
drush php:eval '\Drupal::keyValue("system.schema")->set("domain_config", 10000);' drush updatedbThe patched service runs against the leftover legacy rows. The matched-and-migrated-on-the-first-run rows were already
delete()-d bycleanupLegacyConfigurations(), solistAll()returns only the previously-skipped rows. Theexists()guard protects against stomping any UI overrides set since the first migration.overridable_configurationsis mutated in place additively, existing entries preserved. - Wait for 3.1.x. 3.x's
domain_config_update_10002()auto-installsdomain_config_languageand runsDomainConfigOverrideMigration::migrateConfigurations()against the same legacy rows; the stranded rows are picked up at that point. Collection-name strings are byte-identical across branches (domain.{domain_id},domain.{domain_id}.language.{langcode}), so a site that upgraded directly 3.0.x → 3.1.x without running the recovery lands at the same end state, just later.
Suggested CHANGELOG note for 3.0.1
"Fixed silent skipping of legacy 2.x domain config rows with hyphenated/3-letter langcodes (pt-br, fil, …) or with 3+ segment base config names (system.theme.global, system.image.gd, …) during the 2.x → 3.x migration. Sites that updated to 3.0.0 with overrides of those shapes can recover by resetting the domain_config schema to 10000 and re-running drush updatedb; the legacy rows are still on disk and will be picked up by the patched migration. Sites updating directly from 2.x to 3.0.1+ are not affected."
Tests
Cherry-pick the relevant DomainConfigOverrideMigrationTest coverage from MR !379, adapted to DomainConfigMigration's shape (no separate submodule, registry-write side effect to assert).
Issue fork domain-3589035
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
mably commented