Skip to content

Remove workflows fallback on 4xx errors#3562

Merged
vegaro merged 3 commits into
mainfrom
cesar/status-fallback-gate-workflows
Jun 9, 2026
Merged

Remove workflows fallback on 4xx errors#3562
vegaro merged 3 commits into
mainfrom
cesar/status-fallback-gate-workflows

Conversation

@vegaro

@vegaro vegaro commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

If the workflows list fetch fails, we restore the cache from disk on any error, including 4xx. So a 404 (workflow removed) or 403 (misconfigured key) made the SDK resurrect the previous list from disk and keep serving it.

This PR mirrors what we do for offerings policy. It does not require any backend change.

Fallback behavior (identical to offerings):

Outcome Behavior
Transport failure (no HTTP response) fall back to cached workflows
HTTP 5xx fall back to cached workflows
HTTP 4xx do not fall back
Malformed 2xx body fall back to cached workflows

The disk copy is left untouched on 4xx (no purge).

Doing the same for the workflow detail endpoint will be done in a future PR.


Note

Medium Risk
Changes offerings-gated workflows list caching on network errors; behavior is aligned with offerings and heavily tested but affects paywall/offering mapping when the API returns 4xx.

Overview
Workflows list fetch failures now follow the same fallback rules as offerings, instead of always restoring from disk on any error.

Backend.getWorkflows reports a GetWorkflowsErrorHandlingBehavior with each error: transport failures, 5xx, and malformed success bodies still signal fallback to cached workflows; HTTP 4xx signals SHOULD_NOT_FALLBACK. WorkflowManager uses that signal on list errors—on 4xx it does not read the workflows list or detail envelopes from disk, invalidates the in-memory list freshness so the next call retries, and still completes pending onComplete callbacks so offerings delivery is not blocked. Fallback-eligible errors keep the existing disk restore path, now in restoreWorkflowsListFromDisk. Tests cover the new behavior mapping and the 4xx manager path.

Reviewed by Cursor Bugbot for commit 99321ba. Bugbot is set up for automated code reviews on this repo. Configure here.

@vegaro vegaro added the pr:other label Jun 8, 2026
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.23%. Comparing base (1b6280d) to head (99321ba).

Files with missing lines Patch % Lines
.../kotlin/com/revenuecat/purchases/common/Backend.kt 76.92% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3562      +/-   ##
==========================================
+ Coverage   80.19%   80.23%   +0.04%     
==========================================
  Files         371      371              
  Lines       15239    15252      +13     
  Branches     2112     2114       +2     
==========================================
+ Hits        12221    12238      +17     
+ Misses       2168     2163       -5     
- Partials      850      851       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
@vegaro vegaro force-pushed the cesar/status-fallback-gate-workflows branch from 6258755 to 8283944 Compare June 8, 2026 18:16
@vegaro vegaro changed the title feat: status-based fallback gate for the workflows list Jun 9, 2026
@vegaro vegaro marked this pull request as ready for review June 9, 2026 08:35
@vegaro vegaro requested a review from a team as a code owner June 9, 2026 08:35

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8283944. Configure here.

onErrorHandler(e.toPurchasesError().also { errorLog(it) })
onErrorHandler(
e.toPurchasesError().also { errorLog(it) },
GetWorkflowsErrorHandlingBehavior.SHOULD_FALLBACK_TO_CACHED_WORKFLOWS,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

On iOS I've generalized instead of duplicating the whole logic. But it's fine. We can always follow-up after releasing.

// A 4xx means the server intentionally changed/removed these workflows. Don't
// resurrect a stale list from disk; just settle the callbacks so offerings
// delivery isn't stranded. Caches are left as they are (no restore, no purge).
completePendingCallbacks(appUserID)

@facumenzella facumenzella Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: 4xx leaves the timestamp stale, so we re-hit the backend every call while still serving the stale in-memory map. Before this PR the restore path re-stamped it, so we used to back off.

Want to keep retrying every call, or stamp it so a sticky 4xx backs off? Offerings does the same thing so either way is fine, just flagging.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

let's mimic what offerings does

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

there was actually a divergence with offerings here. In offerings we call forceCacheStale on 4xx errors

…ehavior

A force-refresh returning SHOULD_NOT_FALLBACK left the in-memory timestamp
untouched, so a still-fresh list would suppress retries until TTL expired.
Calling invalidateWorkflowsListTimestamp() mirrors OfferingsManager
calling forceCacheStale() on the same error class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vegaro vegaro enabled auto-merge June 9, 2026 09:52
@vegaro vegaro added this pull request to the merge queue Jun 9, 2026
Merged via the queue into main with commit feaf0a5 Jun 9, 2026
39 checks passed
@vegaro vegaro deleted the cesar/status-fallback-gate-workflows branch June 9, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants