Skip to content

Find remote actors after an update - #4048

Merged
pfefferle merged 13 commits into
trunkfrom
fix/lookup-by-id-hash
Nov 10, 2026
Merged

pfefferle merged 13 commits into
trunkfrom
fix/lookup-by-id-hash

Conversation

@pfefferle

@pfefferle pfefferle commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Proposed changes:

  • WordPress stores a post's guid in two spellings. On insert, & becomes &. On every wp_update_post(), core ignores the guid it is given, reads the stored one in the display context and writes the esc_url() form back (&, and ' as ').
  • Remote_Actors::get_by_uri(), get_existing_uris() and Followers::get_by_uri() only looked for the insert form. So an actor with & or ' in its id was not found after its first refresh, and the next activity created a second actor post, without its followers and blocks.
  • Every spelling a stored id can have (insert form, update form, raw id) now comes from one function, get_stored_id_forms(). Every lookup of a stored id uses it: the guid queries in Remote_Actors, Followers, Inbox, Remote_Posts and Outbox, and the Follow lookups in Following.
  • The plugin also used the stored guid as if it were the actor's id. For an id with & or ' that sent or compared the escaped string. These places now undo that escaping with unescape_guid() before they use the id:
    • the Follow and its outbox lookups (Following), so the remote server recognizes itself as the object;
    • the scheduled actor refresh, which fetched a wrong URL and never matched;
    • the owner check for updates of cached posts (Remote_Posts);
    • the follower sync, Sanitize, the WebFinger lookup, and the REST followers and following lists;
    • the arguments of the activitypub_add_user_block and activitypub_remove_user_block hooks.
    • the user and site-wide actor blocks (Moderation), so a block still applies after the blocked actor was updated;
    • the follower digest and the partial follower list for the follower sync, and the sync endpoint;
    • the owner check for updates of federated comments (Interactions);
    • the outbox Location header and the Stream log.
    • the actor search (autocomplete) for a URL, which now matches every stored spelling.
  • unescape_guid() reverses only the escapes core adds to a guid. The inbox Undo did the same inline before and uses it now too.
  • The Move handler writes a new guid in the insert form, so the lookup finds a moved actor.
  • The comments in Inbox and Remote_Posts said the esc_url() form comes from older releases. Every update writes it, so I fixed them and added the details to AGENTS.md, so we don't have to work this out again.

For ids with & or ', REST clients and hook consumers now get the real id instead of the escaped one. Follow activities sent before this change carry the escaped id, so Following looks for all three spellings.

Not covered:

  • The two spellings can collide between different ids (?a=1&b=2 after an update and a literal ?a=1&b=2). No server writes such ids, so no lookup checks for it. The exact fix would be a lookup by the id hash in post_name for all collections at once, which needs a backfill first because older rows don't have it.
  • Sites that already have a duplicate actor keep it. The lookup returns the older row, and nothing merges the two.
  • The scheduler still passes the guid to the cleanup hooks, which expect a post id. That is a separate bug.

Other information:

  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • Run vendor/bin/phpunit --filter "Test_Remote_Actors|Test_Followers|Test_Following|Test_Remote_Posts|Test_Moderation|test_get_stored_id_forms|test_unescape_guid".
  • test_lookup_after_update_with_special_characters and test_get_by_uri_after_update_with_special_characters create an actor, update it and look it up again, for every id in the shared URI cases. On trunk they fail for the ids with & or '.
  • test_follow_sends_unescaped_actor_id and test_follow_finds_follow_sent_with_escaped_id check the Follow of an actor with & in its id, and that a Follow sent with the escaped id is still found. test_update_accepts_owner_with_escaped_id checks the owner check for updates of cached posts, and test_block_applies_after_actor_update that a block still applies after the blocked actor was updated. Each fails with its fix reverted.

Changelog entry

Entry lives in .github/changelog/fix-remote-actor-lookup-after-update.

@pfefferle pfefferle self-assigned this Nov 10, 2026
@pfefferle
pfefferle requested a review from a team November 10, 2026 16:08
@pfefferle pfefferle added the Bug Something isn't working label Nov 10, 2026
@github-actions github-actions Bot added the [Focus] Compatibility Ensuring the plugin plays well with other plugins label Nov 10, 2026
@pfefferle
pfefferle merged commit a770085 into trunk Nov 10, 2026
12 checks passed
@pfefferle
pfefferle deleted the fix/lookup-by-id-hash branch November 10, 2026 18:04
@pfefferle pfefferle mentioned this pull request Nov 10, 2026
1 of 2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working Docs [Feature] Collections [Feature] REST API [Focus] Compatibility Ensuring the plugin plays well with other plugins [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant