Failing test: preload 4xx must not fire fatal checkoutDidFail#567
Draft
asoules wants to merge 1 commit into
Draft
Failing test: preload 4xx must not fire fatal checkoutDidFail#567asoules wants to merge 1 commit into
asoules wants to merge 1 commit into
Conversation
Documents that handleResponse currently fires checkoutViewDidFailWithError(.checkoutUnavailable, recoverable: false) for any 4xx on a checkout URL, even when the request was a speculative preload (Shopify-Purpose: prefetch). isPreloadRequest is set on the request but never consulted in the response-handling path. This test fails on main; fix to follow.
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.
✨ Caution: AI Generated ✨
What's broken
CheckoutWebView.handleResponse(Sources/ShopifyCheckoutSheetKit/CheckoutWebView.swift) treats any 4xx (and 5xx) response on a checkout URL as a fatal user-facing checkout error, regardless of whether the request was a speculative preload (Shopify-Purpose: prefetch).isPreloadRequestis set on the request (line 234) but never consulted in the response-handling path. Its only other use is a telemetry tag indidFinish(line 400). So a preload that returns 4xx/5xx firesviewDelegate?.checkoutViewDidFailWithError(.checkoutUnavailable, recoverable: false)to the host — even though the buyer never opened the sheet and would have been served fine on the next user-initiated load.This is wrong on its own merits: speculative request failures are not buyer-facing failures.
How it surfaces in production
We hit this during a P1 incident with an enterprise merchant on May 8 2026 (Shopify-internal
inv-22687). Sequence:checkout_sheet_kit_preload_killswitch = trueas a manual assignment (set at some unknown earlier point — kill switches don't auto-expire).Request Forbiddento Sheet Kit prefetches at the edge worker (fast-handler.tsx:579) and the Rails layer (embedded_checkout_concern.rb:18).checkoutViewDidFailWithError(.checkoutUnavailable, recoverable: false)to the host'sCheckoutDelegate.cart_checkout_validationFunction rejected that incomplete destination with a hard "Shipping not available" banner.The killswitch is the trigger that made it visible, but the same pathology exists for any cause of a 4xx during preload: merchant CSP blocking prefetch, WAF rate-limiting prefetch headers specifically, transient backend errors during the warmup window, etc. Fixing it at the SDK layer makes all of those safe.
What this PR contains
A single failing test (
test4xxResponseOnPreloadDoesNotFireFatalError) that pins the desired contract:The test fails on
maintoday. No source change in this PR — happy to follow up with the implementation, or for the team to take it.Suggested fix
Smallest defensible change: early-return at the top of the
>= 400branch inhandleResponse, afterCheckoutWebView.invalidate():15 lines, no behavior change for non-preload requests, all existing 4xx/5xx tests stay green. Existing test coverage:
test401responseOnCheckoutURLCodeDelegationtest403responseOnCheckoutURLCodeDelegationtest404responseOnCheckoutURLCodeDelegationtest410responseOnCheckoutURLCodeDelegationtestTreat5XXReponsesAsRecoverabletest4xxResponseOnPreloadDoesNotFireFatalError(this PR)Considered alternatives:
.preloadFailed(...)— adds public API surface for something hosts can't act on usefully (the next load might succeed; failure here is not actionable for the buyer).Shopify-Purpose: prefetchon the response object — same outcome, more brittle.isPreloadRequestis the source of truth.Cross-platform follow-up
The same pattern likely exists in
Shopify/checkout-sheet-kit-androidandShopify/checkout-sheet-kit-react-native. Will check once this lands.