Opened 2 days ago
Last modified 2 days ago
#66016 new defect (bug)
Race condition in WordPress 7.1 Block Editor: Adding custom field to auto-draft causes duplicated postmeta and forces comment_status to closed
| Reported by: | stanleychan | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Editor | Version: | 7.1 |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: | rest-api |
Description
In WordPress 7.1, a race condition occurs when adding a custom field (via the legacy Custom Fields meta box) to a newly created post (auto-draft status) before its initial save or publish. This results in two critical issues:
- The custom field is inserted twice in the
wp_postmetatable. - The
comment_statusis incorrectly forced toclosed, regardless of the site's default settings.
Steps to Reproduce
- Go to Posts > Add New Post to open the Block Editor (creating an auto-draft).
- Ensure the legacy "Custom Fields" panel is enabled.
- Before saving as a draft or publishing, immediately add a new custom field and value.
- Click Publish or Save Draft.
- Check the post metadata and discussion settings.
Expected Behavior
- The custom field should be saved only once.
- The post's
comment_statusshould inherit the site's default setting (usuallyopen).
Actual Behavior
- The custom field is duplicated (two identical key-value pairs in
wp_postmeta). - The
comment_statuschanges toclosed.
Change History (2)
This ticket was mentioned in PR #13349 on WordPress/wordpress-develop by @ShameemReza.
2 days ago
#2
- Keywords has-patch has-unit-tests added
Adding a custom field to a new post (auto-draft) through the legacy Custom Fields meta box inserts the meta twice and switches the post's comment and ping status to closed.
wp_ajax_add_meta() promotes the auto-draft to a draft through edit_post(). That function reads metakeyinput and metavalue from $_POST, which the AJAX request body contains, so the meta is added there first. Control then returns to wp_ajax_add_meta(), which calls add_meta() again. The same edit_post() call passes no comment_status or ping_status, so _wp_translate_postdata() defaults both to closed. The block editor only sends comment_status over the REST API when the Discussion panel was changed, so the closed status sticks after publishing.
This change promotes the auto-draft with wp_update_post() instead. The meta is inserted exactly once, the post keeps its comment and ping status, and the _edit_last meta is still set as before.
Includes unit tests covering both symptoms. They fail against the current code and pass with this change:
1) test_adding_meta_to_an_auto_draft_should_not_duplicate_the_meta
Failed asserting that two arrays are identical.
Array &0 (
0 => 'testvalue'
+ 1 => 'testvalue'
)
2) test_adding_meta_to_an_auto_draft_should_not_change_the_comment_and_ping_status
The comment status of the post should not have changed.
-'open'
+'closed'
To reproduce manually: on a clean install with the Custom Fields panel enabled, open Posts > Add New, add a custom field before saving, then check wp_postmeta and the post's comment_status.
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Used for: diagnosis, draft patch, and test suggestions. I reviewed, tested, and edited the final change.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
I could reproduce both issues on a clean WordPress 7.1 install (wp-env, no plugins, default theme), and this is not actually a race condition. One click of "Add Custom Field" on an auto-draft triggers both, every time, before the post is even saved.
Clicking that button fires an
add-metaAJAX request. Since the post is still an auto-draft,wp_ajax_add_meta()promotes it to a draft by callingedit_post()with a minimal$post_dataarray. Two things go wrong inside that call:edit_post()unconditionally callsadd_meta( $post_id )near the end, andadd_meta()readsmetakeyinput/metavaluestraight from$_POST. The AJAX request body contains exactly those fields, so the meta is inserted a first time. Control then returns towp_ajax_add_meta(), which callsadd_meta()itself. That's the second insert. Both rows exist inwp_postmetabefore you click Save draft or Publish.$post_dataarray passed toedit_post()contains nocomment_statusorping_status, so_wp_translate_postdata()defaults both toclosed. That logic exists to handle unchecked checkboxes on the full edit form, but here it runs against a request that never had those checkboxes.Database state right after the AJAX call, with the editor still showing an unsaved auto-draft:
The classic editor has the same behavior at AJAX time. I verified with the Classic Editor plugin: identical duplicate rows and
closedstatuses appear the moment the field is added. The difference is that publishing from the classic editor submits the full form, including the pre-checked discussion checkboxes, socomment_statusflips back toopenon save. The block editor saves over the REST API and only sendscomment_statusif you touched the Discussion panel, so theclosedvalue sticks. The duplicate rows survive in both editors. The classic UI only lists the second row, which is why this went unnoticed.This is also not new in 7.1. The auto-draft branch of
wp_ajax_add_meta()has been in place since at least 2014, so Version on this ticket mostly reflects where it was first noticed.Related: ticket:11465 tracks the form-submit flavor of custom field duplication (Preview then Publish without clicking Add). attachment:11465.4.diff:ticket:11465 on that ticket works around the same double add inside
wp_ajax_add_meta()by unsetting the meta keys from$_POST, but it doesn't address the comment status side.A cleaner fix for both symptoms at once: in the auto-draft branch, promote the post with
wp_update_post()directly instead of going throughedit_post(). The post keeps its existing comment and ping status, nothing reads the meta fields out of$_POSTa second time, and the explicitadd_meta()call becomes the only insert.I'll put together a PR against the GitHub mirror with unit tests covering both cases.