Ultimate-Multisite / Ultimate-Multisite/ultimate-multisite
Site duplication can silently abort mid-copy on a wp_options key collision, leaving new sites unthemed/unconfigured (race with domain verification)
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 214
- Forks
- 86
- Avg merge
- 5h 29m
- Merged PRs (30d)
- 60
Description
Summary
MUCD_Data::db_copy_tables() in inc/duplication/data.php drops and recreates each destination table, then runs a plain INSERT ... SELECT to copy rows from the source (template) site's table:
// Populate database with data from source table
self::do_sql_query('INSERT `' . $table_name . '` SELECT * FROM `' . $table . '`');
This statement has no IGNORE / ON DUPLICATE KEY UPDATE. If the freshly-created destination table receives even a single row with a colliding unique key (e.g. option_name in {prefix}_options) before this INSERT runs, the entire INSERT is aborted and none of the source site's rows for that table are copied.
How the collision happens
We observed this in production (WordPress Multisite + network-active security/utility plugins). Immediately after a new site is registered, Ultimate Multisite's own domain-verification loopback check fires an HTTP request against the brand-new site's front-end, in the same window as (or interleaved with) copy_data(). That front-end hit bootstraps WordPress for the new blog ID, and network-active plugins/WP core populate a handful of default wp_options rows (e.g. uninstall_plugins, or a security plugin's own *_version option) via their normal init hooks. If that happens between the destination table's CREATE and the bulk INSERT, the INSERT collides on that one row and aborts entirely — not just for that row.
Confirmed via site-duplication-errors.log on two separate site creations (different colliding option each time):
[...] [ERROR] Got error "Table '...bdsc_10_loginizer_logs' doesn't exist" while running: INSERT `bdsc_10_loginizer_logs` SELECT * FROM `bdsc_3_loginizer_logs`
[...] [ERROR] Got error "Duplicate entry 'loginizer_version' for key 'option_name'" while running: INSERT `bdsc_10_options` SELECT * FROM `bdsc_3_options`
...
[...] [ERROR] Got error "Duplicate entry 'uninstall_plugins' for key 'option_name'" while running: INSERT `bdsc_11_options` SELECT * FROM `bdsc_3_options`
site-duplication.log shows the duplication attempt was reported as "successful" both times despite the options table copy having been aborted — the new site ends up as a bare default-theme WordPress install instead of a clone of the template site (wrong template/stylesheet, missing content, missing plugin configuration), with no error surfaced to the admin beyond the (misleading) "Domain verification failed via loopback ... DNS propagation not finished" notice, which is actually just a downstream symptom of the broken site returning an error page instead of the expected response.
This isn't deterministic — most site creations succeed, since the race window is small. But on any network with several network-active plugins that write options/tables on blog init, it will eventually hit.
Suggested fix
Make the bulk copy tolerant of a partial pre-existing row instead of all-or-nothing, e.g.:
self::do_sql_query('INSERT IGNORE `' . $table_name . '` SELECT * FROM `' . $table . '`');
This keeps the (harmless, auto-regenerated) destination-side default row when there's a collision, while still copying every other row from the template site — turning a full clone failure into, at worst, one skipped option. We applied this one-line change locally and it resolved the issue for us across several subsequent site creations.
A more complete fix might also look at not firing the domain-verification loopback request until after copy_data() completes, to shrink/eliminate the race window itself, but the INSERT IGNORE change alone is enough to prevent data loss.
Environment
- Ultimate Multisite 2.16.1
- WordPress Multisite, subdomain-based
- MySQL/MariaDB via standard shared hosting (HostGator)
- Reproduced with Loginizer + Loginizer Pro network-active (first occurrence) and again after deactivating Loginizer Pro, colliding instead on WordPress core's
uninstall_pluginsoption (second occurrence) — confirming the root cause is the missingIGNORE, not any specific third-party plugin.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in inc/duplication/data.php at MUCD_Data::db_copy_tables() and its do_sql_query call that copies each source table. Reproduce or inspect the logged wp_options collision, then verify that a pre-existing destination row no longer aborts the rest of the table copy and that duplication completes with the template configuration intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- mariadb, mysql, php
- Domain
- backend, database
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100