Opened 8 days ago
Last modified 7 days ago
#66351 new defect (bug)
Notes: A reply or status change to a deleted note is saved and lost
| Reported by: | pranjalpratapsingh | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | REST API | Version: | 6.9 |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: |
Description
Originally reported in Gutenberg: https://github.com/WordPress/gutenberg/issues/80678 | Reported by @mcliwanow
Problem
Two editor sessions can disagree about whether a note still exists. When one session deletes a note while another still has it open, the second session can keep changing it, and the REST API accepts every change:
- Replying to the deleted note succeeds and the editor announces "Reply added.", but the reply is stored under the trashed note and is no longer shown anywhere in the editor.
- Reopen & Reply on a resolved note sets the note's status, which moves it out of the trash without the replies that were trashed along with it, so the thread comes back with only the new reply.
- Resolving or editing a deleted note is accepted in the same way.
This is distinct from #64240, which trashes the replies that already exist when a top-level note is trashed. Here, the change arrives after the delete has completed.
WP_REST_Comments_Controller::create_item() already rejects a reaction whose parent note is trashed or spam ("A reaction cannot be added to a trashed or spam note."), but not a note reply. update_item() changes the status or content of a trashed note like any other comment.
Steps to reproduce
In the editor:
- Sign the same administrator into two browser sessions, A and B (for example a normal and a private window).
- In A, create a post, add a Paragraph, add a note to it, and save the draft.
- Open the same post in B and open the note.
- In A, select the note and type a reply, but don't submit it.
- In B, delete the note from its Actions menu and wait for "Note deleted.".
- In A, without reloading, click Reply.
Result: "Reply added." After a reload, the reply is not shown, but it is stored with the deleted note as its parent.
For Reopen & Reply, resolve the note in A before step 4. The button then reads "Reopen & Reply", and clicking it brings back the deleted note as open, while its earlier replies stay in the trash.
The same thing from the browser console, in a single session, on a saved draft in the editor:
( async () => { const post = wp.data.select( 'core/editor' ).getCurrentPostId(); const api = ( path, method, data ) => wp.apiFetch( { path, method, data } ); const note = await api( '/wp/v2/comments', 'POST', { post, content: 'Note', status: 'hold', type: 'note' } ); await api( `/wp/v2/comments/${ note.id }`, 'DELETE' ); // Moves the note to the trash. // 1. A reply to the deleted note is accepted. const reply = await api( '/wp/v2/comments', 'POST', { post, content: 'Reply', status: 'hold', type: 'note', parent: note.id } ); console.log( 'Reply stored under trashed note:', reply.id, 'parent', reply.parent ); // 2. Reopening the deleted note moves it out of the trash. const reopened = await api( `/wp/v2/comments/${ note.id }`, 'POST', { status: 'hold' } ); console.log( 'Deleted note is now:', reopened.status ); } )();
Expected
- Creating a note reply whose parent is trashed, spam, or no longer exists returns an error (for example
400 rest_comment_invalid_parent), as it already does for reactions. - Any change to a trashed or spam note other than restoring it (
status: untrashorunspam) returns an error, so the editor can tell the user instead of losing their change. Undo in the editor usesuntrash, so it keeps working.
The editor already shows REST error messages in a snackbar and keeps the typed text when a save fails, so no client changes are needed.
Fix
A fix for the Gutenberg plugin's WordPress 7.2 compat controller, with tests, is in https://github.com/WordPress/gutenberg/pull/85202. A Core patch with the same changes and PHPUnit tests will follow on this ticket. Suggesting the 7.2 milestone, since 7.2 also adds emoji reactions to notes in the same controller.
Change History (1)
This ticket was mentioned in PR #14560 on WordPress/wordpress-develop by @pranjalpratapsingh.
7 days ago
#1
- Keywords has-patch has-unit-tests added
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Another editor session can delete a note while it is still open. The REST API then accepts changes to the deleted note that the editor can no longer show:
201, and the editor announces "Reply added.", but the reply is stored under the trashed note and is not shown anywhere.This PR changes
WP_REST_Comments_Controller:create_item()rejects a note reply whose parent is trashed, spam, or no longer exists, with400 rest_comment_invalid_parent. Reactions are already rejected on a trashed or spam note.update_item()rejects any change to a trashed or spam note other than restoring it (statusofuntrashorunspam), with400 rest_comment_invalid_status. Undo in the editor sendsuntrash, so it keeps working.The editor already shows REST error messages in a snackbar, and keeps the note form's text when a save fails, so no client changes are needed. The same change is in the Gutenberg plugin's 7.2 compat controller: https://github.com/WordPress/gutenberg/pull/85202. Originally reported in https://github.com/WordPress/gutenberg/issues/80678.
Testing Instructions
For Reopen & Reply, resolve the note in A before step 4. Clicking Reopen & Reply after the delete:
The console snippet in the Trac ticket reproduces both in a single session. With this PR, its first request fails with
400.Unit tests:
npm run test:php -- --group 66351Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Reproducing both issues in two browser sessions on trunk, writing the patch and the PHPUnit tests, and drafting this description.
🤖 Generated with Claude Code