Skip to content

Self-heal stale image blob refs on publish#143

Draft
pfefferle wants to merge 6 commits into
trunkfrom
fix/stale-featured-image-blob
Draft

Self-heal stale image blob refs on publish#143
pfefferle wants to merge 6 commits into
trunkfrom
fix/stale-featured-image-blob

Conversation

@pfefferle

Copy link
Copy Markdown
Member

Proposed changes:

Backfilling posts that have a featured image (or in-body images) to a self-hosted PDS fails with atmosphere_pds: Could not find blob: bafkrei… (status 400). A blob's CID is cached in _atmosphere_blob_ref postmeta and reused on later publishes without re-uploading — but a blob is only retrievable from the PDS it was uploaded to, and only while a committed record references it. So when the active account/PDS changes (the reported case: published to Bluesky's PDS first, then switched to a self-hosted instance and backfilled), or the reference PDS garbage-collects an orphaned blob, the cached CID points at a blob the current PDS has never seen and applyWrites rejects the whole batch.

This PR automates the manual delete_post_meta($id, '_atmosphere_blob_ref') workaround:

  • publish_post() now detects a "Could not find blob" 400 from applyWrites, drops the cached refs for every image the post embeds (featured image and in-body core/image attachments), and retries the publish once so the image is re-uploaded against the current PDS.
  • The build/publish body was extracted into attempt_publish_post() so the retry runs with fresh transformers (re-doing the blob upload the first attempt short-circuited from cache). reconcile_post_after_write() and atmosphere_publish_post_result still fire exactly once per call.
  • New Post::forget_image_blob() / Post::forget_post_image_blobs() / Post::embedded_image_attachment_ids() helpers keep the "which attachments does a publish upload" knowledge inside the transformer.
  • The retry is structurally bounded to one re-attempt (no loop); a persistently-missing blob surfaces the error instead of retrying forever. Both the retry and the no-images-to-retry case log a debug_log() breadcrumb.

Reported on the WordPress.org support forums: https://wordpress.org/support/topic/backfilled-posts-with-featured-images-fail-400-could-not-find-blob/ — the reporter confirmed they originally published to Bluesky's PDS and then switched to their own instance before backfilling.

Other information:

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

Testing instructions:

Automated:

  • npm run env-test -- --filter='test_publish_post_self_heals_stale_blob_ref|test_publish_post_self_heals_stale_inline_image_blob|test_publish_post_blob_self_heal_retries_only_once' — covers featured-image self-heal, in-body-image self-heal, and the single-retry ceiling.
  • Full suite: composer lint && npm run env-test (686 tests pass).

Manual (reproduces the report):

  1. Connect ATmosphere to a PDS and publish a post that has a featured image, so _atmosphere_blob_ref is cached on the attachment.
  2. Disconnect and reconnect to a different PDS/account (e.g. a self-hosted ghcr.io/bluesky-social/pds).
  3. Backfill / re-publish that post.
  4. Before: fails with Could not find blob … (status 400). After: the image is re-uploaded to the new PDS and the post publishes (with a debug_log line noting the recovery when WP_DEBUG is on).

Changelog entry

A changelog entry is included at .github/changelog/fix-stale-featured-image-blob (Patch / Fixed).

When a cached blob CID points at a blob the active PDS no longer holds
(account/PDS switch, or orphaned-blob garbage collection on the reference
PDS), applyWrites rejects the publish with "Could not find blob". Drop
the cached refs for every image the post embeds and retry the publish
once so the post lands without manually clearing _atmosphere_blob_ref.

Reported at https://wordpress.org/support/topic/backfilled-posts-with-featured-images-fail-400-could-not-find-blob/
@pfefferle pfefferle self-assigned this Jun 17, 2026
@pfefferle pfefferle requested a review from a team June 17, 2026 16:03
@github-actions github-actions Bot added [Feature] Publisher Publishing to AT Protocol [Feature] Transformer AT Protocol record transformers [Status] In Progress [Tests] Includes Tests PR includes test changes labels Jun 17, 2026
@pfefferle pfefferle requested a review from Copilot June 17, 2026 16:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds an automatic recovery path for publish/backfill failures caused by stale cached image blob CIDs (_atmosphere_blob_ref) that no longer exist on the currently connected PDS. On a specific applyWrites 400 “Could not find blob” error, Publisher now clears cached blob refs for all images the post may embed and retries the publish once so images are re-uploaded to the active PDS.

Changes:

  • Added a one-time retry in Publisher::publish_post() that self-heals “Could not find blob” failures by clearing cached image blob refs before retrying.
  • Introduced transformer helpers (forget_* + embedded_image_attachment_ids()) to centralize “which attachments can be uploaded during publish” logic.
  • Added PHPUnit coverage for featured-image and inline-image self-heal behavior, including the “retry only once” ceiling.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
includes/class-publisher.php Adds bounded self-heal retry on blob-missing errors and refactors publish execution into attempt_publish_post().
includes/transformer/class-post.php Adds helpers to forget cached blob refs and enumerate attachment IDs whose blobs may be uploaded.
tests/phpunit/tests/class-test-publisher.php Adds tests for stale blob ref recovery (featured + inline images) and single-retry limit.
.github/changelog/fix-stale-featured-image-blob Adds a patch-level changelog entry describing the fix.

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

Comment thread includes/class-publisher.php Outdated
WP_Error::get_error_data() can return a non-array; array-offset access on
a string fatals on PHP 8 before the ?? defaults. Check is_array() first.
Blob-backed document content formats (Leaflet, pckt) upload a blob per
core/image block, so the self-heal must clear every in-body image's
cached ref, not just the four the Bluesky images embed uses. Otherwise a
stale ref on the fifth-or-later image survives and the retry fails again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Publisher Publishing to AT Protocol [Feature] Transformer AT Protocol record transformers [Status] In Progress [Tests] Includes Tests PR includes test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants