{"record":{"id":"fcb7cb3d91899298","repo":"TryGhost/Ghost","slug":"timed-out-waiting-for-stripe-sync-for-tier-tier","errorCode":null,"errorMessage":"Timed out waiting for Stripe sync for tier ${tier.name}","messagePattern":"Timed out waiting for Stripe sync for tier (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"e2e/helpers/playwright/flows/tiers.ts","lineNumber":46,"sourceCode":"    const intervalMs = 250;\n    const startTime = Date.now();\n\n    while ((Date.now() - startTime) < timeoutMs) {\n        const product = opts.stripe.getProducts().find(item => item.name === tier.name);\n        const syncedPrices = product\n            ? opts.stripe.getPrices().filter(item => item.product === product.id).length\n            : 0;\n\n        if (syncedPrices === 2) {\n            return tier;\n        }\n\n        await new Promise<void>((resolve) => {\n            setTimeout(resolve, intervalMs);\n        });\n    }\n\n    throw new Error(`Timed out waiting for Stripe sync for tier ${tier.name}`);\n}\n","sourceCodeStart":28,"sourceCodeEnd":48,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/helpers/playwright/flows/tiers.ts#L28-L48","documentation":"Thrown by the tiers sync-wait loop after polling for Stripe price sync and never observing the expected count (2 prices per product). Each iteration counts prices whose `product` matches the tier's Stripe product id; if it never reaches 2 within the configured deadline, the loop exhausts and throws.","triggerScenarios":"A tier is created/edited and the test waits for Stripe to sync its monthly + yearly prices, but the Stripe product never accumulates two prices within the polling window. Caused by Stripe webhook not delivering price updates, or the product/prices not being created in the test Stripe instance.","commonSituations":"Stripe test service is disconnected; webhook for `price.created` not delivered or processed; the tier was saved without a valid Stripe connection; polling interval/timeout too short for a slow environment; only one cadence was configured on the tier.","solutions":["Confirm Stripe Connect is set up in the test environment before creating tiers.","Verify the tier is configured with both monthly and yearly pricing (two prices expected).","Check the Stripe test service webhook client is delivering product/price events.","If the environment is slow, increase the polling timeout/interval rather than letting it exhaust."],"exampleFix":"// before\nawait new Promise<void>((resolve) => { setTimeout(resolve, intervalMs); });\n// ... after loop ...\nthrow new Error(`Timed out waiting for Stripe sync for tier ${tier.name}`);\n\n// after — extend the deadline and surface progress\nconst deadline = Date.now() + maxWaitMs;\nwhile (Date.now() < deadline) {\n    const synced = stripe.getPrices().filter(p => p.product === product.id).length;\n    if (synced === 2) return tier;\n    await new Promise(r => setTimeout(r, intervalMs));\n}\nthrow new Error(`Timed out waiting for Stripe sync for tier ${tier.name} (found ${stripe.getPrices().filter(p => p.product === product.id).length} prices)`)\";","handlingStrategy":"retry","validationCode":null,"typeGuard":"function tierHasTwoPrices(tier: {stripe_product_id?: string}, stripe: {getPrices(): {product: string}[]}): boolean {\n    const count = stripe.getPrices().filter(p => p.product === tier.stripe_product_id).length;\n    return count === 2;\n}","tryCatchPattern":null,"preventionTips":["Set up Stripe Connect before creating tiers.","Configure both monthly and yearly prices on the tier.","Confirm the webhook client delivers product/price events.","Size the polling timeout to the environment's typical sync latency."],"tags":["stripe","e2e","tiers","webhooks","polling","race-condition"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}