Skip to content

Strip ephemeral _edit_lock postmeta on db-apply#266

Open
epeicher wants to merge 1 commit into
trunkfrom
fix/strip-edit-locks-on-db-apply
Open

Strip ephemeral _edit_lock postmeta on db-apply#266
epeicher wants to merge 1 commit into
trunkfrom
fix/strip-edit-locks-on-db-apply

Conversation

@epeicher

@epeicher epeicher commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

What it does

A site pulled with reprint no longer shows a phantom "X is currently editing" badge in wp-admin. db-apply now strips the remote site's editor locks before marking the import complete.

Rationale

A WordPress SQL dump captures _edit_lock postmeta verbatim. _edit_lock is ephemeral runtime state, recording that an editor had a given post open at the moment of the dump (its value is timestamp:user_id). On the imported copy the lock is meaningless, the remote editing session has nothing to do with the local site, but WordPress still renders it as a "currently editing" badge in the posts list for up to ~150 seconds after the captured timestamp whenever the pull ran while a post was open remotely.

Implementation

Added strip_ephemeral_edit_locks(PDO $pdo), called at the end of run_db_apply while the target PDO is still open (right after host-plugin deactivation, before the state is marked complete):

$postmeta_table = '`' . str_replace('`', '``', $table_prefix . 'postmeta') . '`';
$deleted = $pdo->exec("DELETE FROM {$postmeta_table} WHERE meta_key = '_edit_lock'");
// The dump runs with AUTOCOMMIT=0 and leaves it off, so the DELETE needs an explicit COMMIT.
$pdo->exec('COMMIT');
  • The table prefix comes from preflight data (defaults to wp_); the name is backtick-quoted with ` doubling to prevent injection from a crafted prefix.
  • Only _edit_lock rows are removed; all other postmeta is untouched. WordPress regenerates the lock locally on the next edit, and core's own WXR exporter skips it too, so nothing is lost.
  • A dump without a postmeta table is tolerated: the whole operation is wrapped in try/catch and logged to the audit log, never fatal.

Testing instructions

The easiest way to reproduce the issue is to use Studio code:

  • Open the remote site, add a Post and publish it, but don't close the window.
  • Navigate to Studio repo root and run:
export STUDIO_ENABLE_PULL_REPRINT=true && node ~/github/studio/apps/cli/dist/cli/main.mjs pull-reprint --url <your_wpcom_site>
  • Reply Y to create a new Studio site
  • Once the site is created, you can open the URL for the WP Admin returned by the Reprint CLI
  • Observe that the created post is there with a Currently being edited message
CleanShot 2026-06-18 at 11 20 10@2x
  • Apply the changes on this branch, run composer build:phar, copy that to Studio repo by running cp reprint.phar <studio_repo>/wp-files/reprint
  • On Studio, run npm run cli:build so the phar file is copied to the dist folder
  • Delete the site to start from scratch and repeat the first three steps
  • Observe that the created post does not have the Currently being edited message

A WordPress SQL dump captures `_edit_lock` postmeta verbatim. That meta is
runtime state recording that an editor had a post open at dump time. It is
meaningless on the imported copy, but WordPress still renders it as a
phantom "X is currently editing" badge for up to 150 seconds after the
captured timestamp when the pull ran while a post was open remotely.

Add strip_ephemeral_edit_locks(), called at the end of db-apply while the
target PDO is still open, to DELETE every `_edit_lock` row. Other postmeta
is left intact, and a dump without a postmeta table is tolerated. WordPress
regenerates the lock locally on the next edit and core's WXR exporter skips
it too, so nothing is lost.
@github-actions

Copy link
Copy Markdown
Contributor

Pull pipeline performance — large-directory

Site: large-directory · 2,000+ plus targeted file-transfer scenarios files · 10,000 posts · 25,000 postmeta · PHP 8.5.7

Stage PR trunk Δ Status Details
playground-sqlite-db-pull 9.19 s 9.40 s ⚪ -217 ms (-2.3%) condition=db-pull in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=lexer
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=selected
trunk: condition=db-pull in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=lexer
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=selected
playground-sqlite-db-apply 3.60 s 3.57 s ⚪ +30 ms (+0.8%) condition=db-apply to SQLite in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=parser
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=verified
native_ast=WP_MySQL_Native_Parser_Node
sqlite_driver_parser=verified
trunk: condition=db-apply to SQLite in PHP.wasm
runtime=php.wasm 8.3
wp_mysql_parser=enabled
mode=parser
native_lexer=verified
native_token_stream=WP_MySQL_Native_Token_Stream
native_token_count=18
native_parser=verified
native_ast=WP_MySQL_Native_Parser_Node
sqlite_driver_parser=verified
Total 12.78 s 12.97 s ⚪ -187 ms (-1.4%)

Numbers carry runner noise; treat single-run deltas as directional, not authoritative.

📈 Trunk performance history — commit-by-commit timeline.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strips ephemeral _edit_lock WordPress postmeta rows during the db-apply phase of a reprint import. These rows represent remote editing sessions captured in the SQL dump and cause phantom "X is currently editing" badges in wp-admin on the imported copy.

Changes:

  • Added strip_ephemeral_edit_locks(PDO $pdo) private method that DELETEs _edit_lock rows from the postmeta table, with proper SQL injection prevention, explicit COMMIT, and non-fatal error handling.
  • Integrated the new method call at the end of run_db_apply, after host plugin deactivation and before marking the import complete.
  • Added an integration test (DbApplyEditLockTest) that applies a dump containing an _edit_lock row to a SQLite target and verifies the lock is stripped while other postmeta is preserved.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/reprint-importer/src/import.php Adds strip_ephemeral_edit_locks() method and calls it at end of run_db_apply to remove _edit_lock postmeta rows
tests/Import/DbApplyEditLockTest.php New integration test verifying _edit_lock removal and _thumbnail_id preservation through full db-apply flow

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@adamziel

Copy link
Copy Markdown
Collaborator

This is okay and we could get it in. I'm also looking at an alternative that wouldn't insert those rows in the first place so that we won't have to delete them later.

@epeicher

epeicher commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

This is okay and we could get it in. I'm also looking at an alternative that wouldn't insert those rows in the first place so that we won't have to delete them later.

Great, thanks for the review @adamziel. I will wait to merge this as it is just cosmetic, and your suggestion seems better to me. Please let me know your findings, and we can either close this or proceed with it.

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.

3 participants