Skip to content

fix: track seen blocks to prevent infinite recursion in cache checks#2327

Merged
jason10lee merged 4 commits intotrunkfrom
fix/recursion-guard
Apr 1, 2026
Merged

fix: track seen blocks to prevent infinite recursion in cache checks#2327
jason10lee merged 4 commits intotrunkfrom
fix/recursion-guard

Conversation

@jason10lee
Copy link
Copy Markdown
Contributor

@jason10lee jason10lee commented Mar 30, 2026

All Submissions:

Changes proposed in this Pull Request:

A synced pattern containing a core/block reference to itself (or a cycle of patterns referencing each other) causes infinite recursion in Newspack_Blocks_Caching::check_block_cache_status(). The method recurses into reusable block content via get_post() + parse_blocks(), but never checks whether it has already visited a given block ID. This leads to an OOM crash on the front end for logged-out visitors, since logged-in admins bypass the cache path entirely.

This was observed on a live site where a synced pattern accidentally contained a self-referencing <!-- wp:block {"ref":14406} /-->, causing 500 errors on any page that embedded it.

This PR adds a static $visited_reusable_blocks tracker that mirrors the approach used by WordPress core's render_block_core_block() (source). When a reusable block ID is encountered a second time during the same traversal, it is skipped. The visited set is scoped per top-level call: entries are added before recursing and removed after, so the same block can still appear in unrelated branches of the block tree.

Also adds a guard for empty ref attributes to avoid passing a falsy value to get_post() (because of course it'll happen!).

Addresses NPPM-2706 Guard against infinite recursion in included blocks.

How to test the changes in this Pull Request:

Setup:

ncd newspack-blocks   # or: cd repos/newspack-blocks
n ci-build

1. Verify existing tests pass:

n test-php

Manual testing (browser)

2a. Create a recursive synced pattern:

  1. In wp-admin, go to Appearance → Editor → Patterns and create a new synced pattern.
  2. Add a Homepage Posts block. Publish the pattern and note its post ID (visible in the URL: post=<ID>).
  3. Edit the same pattern and switch to the Code Editor. Append a self-reference:
    <!-- wp:block {"ref":<ID>} /-->
    
  4. Save the pattern.

3a. Verify the fix:

  1. Create or edit a post and insert the recursive pattern.
  2. Publish the post and view it in a logged-out browser (or incognito window).
  3. Before this fix: PHP fatal error (max nesting depth exceeded), HTTP 500. After: the page renders normally; the self-referencing block is silently skipped.

4a. Confirm non-recursive patterns still render:

  1. Create two separate synced patterns, each containing a Homepage Posts block.
  2. Embed both in a post and view it logged-out.
  3. Both patterns should render their content normally.

Alternative: CLI testing using newspack-workspace

2b. Create a self-referencing synced pattern and verify the fix prevents a crash:

# Create a synced pattern containing a Homepage Posts block.
PATTERN_ID=$(docker exec newspack_dev sh -c "wp post create \
  --post_type=wp_block \
  --post_status=publish \
  --post_title='Recursion Test Pattern' \
  --post_content='<!-- wp:newspack-blocks/homepage-articles /-->' \
  --porcelain \
  --allow-root")
echo "Created pattern: $PATTERN_ID"

# Inject a self-reference into the pattern.
docker exec newspack_dev sh -c "wp post update $PATTERN_ID \
  --post_content='<!-- wp:newspack-blocks/homepage-articles /--><!-- wp:block {\"ref\":$PATTERN_ID} /-->' \
  --allow-root"

# Create a published post that embeds the recursive pattern.
TEST_POST_ID=$(docker exec newspack_dev sh -c "wp post create \
  --post_type=post \
  --post_status=publish \
  --post_title='Recursion Guard Test' \
  --post_content='<!-- wp:block {\"ref\":$PATTERN_ID} /-->' \
  --porcelain \
  --allow-root")
TEST_URL=$(docker exec newspack_dev sh -c "wp post list \
  --post__in=$TEST_POST_ID --field=url --allow-root")
echo "Test URL: $TEST_URL"

# Fetch the page as a logged-out visitor and check for a successful response.
# Before this fix: HTTP 500 (PHP fatal from max nesting depth).
# After this fix: HTTP 200 with rendered content.
HTTP_STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$TEST_URL")
echo "HTTP status: $HTTP_STATUS"  # Expect: 200

3b. Confirm non-recursive synced patterns still render normally:

# Create two independent synced patterns.
PATTERN_A=$(docker exec newspack_dev sh -c "wp post create \
  --post_type=wp_block \
  --post_status=publish \
  --post_title='Pattern A' \
  --post_content='<!-- wp:newspack-blocks/homepage-articles /-->' \
  --porcelain \
  --allow-root")
PATTERN_B=$(docker exec newspack_dev sh -c "wp post create \
  --post_type=wp_block \
  --post_status=publish \
  --post_title='Pattern B' \
  --post_content='<!-- wp:newspack-blocks/homepage-articles /-->' \
  --porcelain \
  --allow-root")

# Create a post that embeds both (no cycle).
NORMAL_POST_ID=$(docker exec newspack_dev sh -c "wp post create \
  --post_type=post \
  --post_status=publish \
  --post_title='Non-Recursive Test' \
  --post_content='<!-- wp:block {\"ref\":$PATTERN_A} /--><!-- wp:block {\"ref\":$PATTERN_B} /-->' \
  --porcelain \
  --allow-root")
NORMAL_URL=$(docker exec newspack_dev sh -c "wp post list \
  --post__in=$NORMAL_POST_ID --field=url --allow-root")

# Both patterns should render. Check for HTTP 200 and presence of block markup.
HTTP_STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$NORMAL_URL")
echo "HTTP status: $HTTP_STATUS"  # Expect: 200
curl -s "$NORMAL_URL" | grep -q 'wp-block-newspack-blocks-homepage-articles' \
  && echo "Block markup found" || echo "Block markup MISSING"

4. Cleanup (optional):

docker exec newspack_dev sh -c "wp post delete $PATTERN_ID $TEST_POST_ID \
  $PATTERN_A $PATTERN_B $NORMAL_POST_ID --force --allow-root"

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully ran tests with your changes locally?

@jason10lee jason10lee marked this pull request as ready for review March 31, 2026 16:29
@jason10lee jason10lee requested a review from a team as a code owner March 31, 2026 16:29
@jason10lee jason10lee self-assigned this Mar 31, 2026
Copy link
Copy Markdown
Member

@adekbadek adekbadek left a comment

Choose a reason for hiding this comment

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

Looks good! Verified the fix locally:

  • Trunk: self-referencing synced pattern → HTTP 500 (OOM crash) ✗
  • This branch: same pattern → HTTP 200, page renders normally ✓
  • Non-recursive patterns render correctly ✓
  • Mutual recursion (A ↔ B) handled gracefully ✓

Two minor nits inline.

Comment thread includes/class-newspack-blocks-caching.php
Comment thread tests/test-caching.php
@jason10lee jason10lee merged commit 9116fc0 into trunk Apr 1, 2026
11 checks passed
@jason10lee jason10lee deleted the fix/recursion-guard branch April 1, 2026 18:19
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 1, 2026

Hey @jason10lee, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

matticbot pushed a commit that referenced this pull request Apr 2, 2026
## [4.26.1-alpha.1](v4.26.0...v4.26.1-alpha.1) (2026-04-02)

### Bug Fixes

* **modal-checkout:** register NYP AJAX handler for logged-out users ([#2323](#2323)) ([291223e](291223e))
* reinstate WP 7.0 enqueue_block_assets workaround ([#2319](#2319)) ([4cff5a6](4cff5a6))
* track seen blocks to prevent infinite recursion in cache checks ([#2327](#2327)) ([9116fc0](9116fc0))
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

🎉 This PR is included in version 4.26.1-alpha.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

matticbot pushed a commit that referenced this pull request Apr 13, 2026
## [4.26.1](v4.26.0...v4.26.1) (2026-04-13)

### Bug Fixes

* **modal-checkout:** register NYP AJAX handler for logged-out users ([#2323](#2323)) ([291223e](291223e))
* reinstate WP 7.0 enqueue_block_assets workaround ([#2319](#2319)) ([4cff5a6](4cff5a6))
* track seen blocks to prevent infinite recursion in cache checks ([#2327](#2327)) ([9116fc0](9116fc0))
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 4.26.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants