Defer the session cookie until session data is written so anonymous page views stay cacheable#3211
Merged
brianhogg merged 2 commits intoJun 22, 2026
Conversation
LifterLMS emitted the wp_llms_session_<COOKIEHASH> cookie on the first page view of every anonymous visitor, even on pages that never touch the session. Full-page caches won't store responses carrying a Set-Cookie header, so this disabled anonymous page caching site-wide whenever LifterLMS was active. Make the session cookie lazy: a brand-new session lives in memory only (no Set-Cookie, no DB row) until the first write via LLMS_Session::set(), at which point the cookie is emitted. Anonymous visitors who never write session data never receive the cookie, so those responses stay cache-eligible. Mirrors the llms-tracking cookie approach from gocodebox#2330 / gocodebox#2331. Fixes gocodebox#3210 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The headers_sent() guard around the deferred set_cookie() call dropped the cookie whenever output had already started, which broke the new behavior under PHPUnit (and would have failed in CI). The eager cookie was previously emitted from the constructor with no such guard, and every first-party session write runs before output, so emit unconditionally to match that behavior and keep it testable. Seed a cookie in the existing-cookie session tests that previously relied on the constructor eagerly creating one. Verified locally: tests-run --group sessions,coupons -> 65 tests, 452 assertions, OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
LifterLMS emits the
wp_llms_session_<COOKIEHASH>cookie on the first page view of every anonymous visitor, even on pages that never touch the session. Full-page caches won't store responses carrying aSet-Cookieheader, so this disables anonymous page caching site-wide whenever LifterLMS is active.This makes the session cookie lazy. A brand-new session lives in memory only (no
Set-Cookie, no DB row) until the first write viaLLMS_Session::set()(an applied coupon, a queued notice, etc.), at which point the cookie is emitted. Anonymous visitors who never write session data never receive the cookie, so those responses stay cache-eligible. Requests that do write to the session (checkout, coupon AJAX, notice-queuing) still set the cookie and correctly bypass the cache.The cookie is emitted from
set()the same way it was previously emitted from the constructor: unconditionally, during request processing before any output. Every first-party write path (core checkout + AJAX coupons,llms_add_notice(), Advanced Coupons coupon-from-URL, Gifts voucher count, Groups seat count) runs before output, so the cookie is emitted in time to take effect.Mirrors the approach already taken for the
llms-trackingcookie in #2330 / #2331. Default-on, with the existingllms_session_should_initfilter left intact as an escape hatch. Verified against core and all first-party add-ons that every session writer goes throughLLMS_Session::set()and none relies on eager cookie creation.Fixes #3210
How has this been tested?
Ran the PHPUnit suite locally against this branch:
Updated
test_init_cookie_new()to assert a new session emits no cookie until data is written, then emits on firstset(). The existing-cookie tests (test_get_cookie,test_init_cookie_from_existing_expiring,test_init_cookie_from_existing_user_logged_in) previously relied on the constructor eagerly creating a cookie; they now seed one explicitly to match the lazy behavior. The coupon AJAX group exercises the real session-write path and passes.Screenshots
N/A.
Types of changes
Checklist:
.changelogs/anonymous-session-cookie-page-cache.yml).