[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache - #14109

Closed
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris
Closed

[6.x] Fix Stache index re-entrancy causing null URIs on cold stache#14109
o1y wants to merge 1 commit into
statamic:6.xfrom
o1y:fix-stache-null-uris

Conversation

@o1y

@o1yo1y commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Fixesstatamic/seo-pro#508

Deep Stache internals ahead. Please try to break or optimize this.. I did my best 👀

On a cold stache, building an index calls getItem() for each entry, which hits getCachedItem(). That method then triggers ->resolveIndex('uri')->load() causing re-entrant index loading.

The issue: validateTree() sees incomplete indexes, removes all entries from the structure tree, and all URIs become null. The broken index gets cached, poisoning subsequent requests. That's why the sitemap.xml is empty or Entry::query()->whereNotNull('uri') returns null 🕳️

My introduced caching from #14031 also triggers this in single-site setups, since cached items cause getCachedItem() to be called during index builds.

The main change is about tracking nested index loads, instead of storing the currently loading index in a string. ☀️

-private static ?string $currentlyLoading = null;+private static array $loadingStack = [];

It seems also possible to just remove the URI blink from getCachedItem() (or just use the base class function), but this would break the performance optimization which was introduced in #9844

- if (! Index::isLoading() && $this->shouldBlinkEntryUris && ($uri = $this->resolveIndex('uri')->load()->get($entry->id()))) { - Blink::store('entry-uris')->put($entry->id(), $uri); - }

If possible, it might be worth backporting this to 5.x? 🤗

@mmodler

mmodler commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

@o1y Thanks for tracking this down!

We're running ~300 production instances on Statamic v5.73.11 and have been experiencing sporadic cases where all frontend pages return 404. A cache clear resolves the issue each time.

I came across this PR while investigating the root cause of these 404s. After analyzing the code, I'm fairly confident this is the same underlying issue — the affected code in Index.php and CollectionEntriesStore.php is identical between 5.x and 6.x, so this fix should be directly applicable to 5.x as well.

A backport to 5.x would be much appreciated, as this is actively affecting production instances.

@jasonvarga

Copy link
Copy Markdown
Member

Thanks for the input @mmodler. We'd happily backport it.

Unless @o1y you could reopen this against 5.x?

@o1y

o1y commented Mar 9, 2026

Copy link
Copy Markdown
ContributorAuthor

Done ✅

@o1yo1y closed this Mar 9, 2026
@mmodler

Copy link
Copy Markdown
Contributor

You guys rock!

@o1y
o1y deleted the fix-stache-null-uris branch June 20, 2026 16:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sitemap.xml is empty after cache:clear in Multisite

3 participants

@o1y@mmodler@jasonvarga