Skip to content

fix(workflows): Don't serve stale workflows when the backend rejects the request#6946

Merged
facumenzella merged 3 commits into
mainfrom
facu/workflow-list-4xx-no-stale
Jun 8, 2026
Merged

fix(workflows): Don't serve stale workflows when the backend rejects the request#6946
facumenzella merged 3 commits into
mainfrom
facu/workflow-list-4xx-no-stale

Conversation

@facumenzella

@facumenzella facumenzella commented Jun 8, 2026

Copy link
Copy Markdown
Member

Follow-up to #6917.

What

#6917 made the workflows list survive the backend being down: if the /workflows fetch fails, we fall back to the last copy on disk so a cold start can still render the prefetched paywalls.

The problem is we did that on any failure, including a 4xx. A 4xx isn't "the backend is down", it's the backend telling us no (workflows turned off for the app, this user isn't allowed, etc). Serving the stale prefetched data we just got rejected for is the wrong move. So now we only fall back on the transient stuff (5xx, offline); a 4xx skips the restore.

Nothing new to decide here. Offerings already draw this exact line, so I reused their rule instead of inventing one: pulled the classification up into shouldFallBackToCache on NetworkError/BackendError, and the offerings property just delegates to it now (so offerings behaviour is identical, OfferingsManager untouched).

flowchart TD
    A["getWorkflowsList: /workflows fetch"] --> B{result}
    B -->|success| C[cache list + prefetch details]
    B -->|failure| D{4xx?}
    D -->|"yes, backend rejected it"| E["skip restore<br/>(don't serve stale)"]
    D -->|"no, 5xx / offline"| F["restore list + details from disk<br/>(render offline)"]
    E --> G([onComplete])
    F --> G
    C --> G
Loading

It only gates the disk restore, it doesn't wipe anything already in memory (same as offerings).

AI session context

Metadata

Goal

Stop the workflows list-fetch failure path from restoring stale prefetched data on a 4xx, while keeping the 5xx / offline backend-down recovery from #6917. Workflows-side + shared error helper only.

Initial Prompt

Identified as a missing follow-up from #6917 (its original body listed a "status-based fallback gate so a 4xx doesn't restore" non-goal). The human picked it up: "Let's tackle 2."

Agent Contribution

  • Found the SDK's existing offerings classification (shouldFallBackToCachedOfferings: 5xx/offline fall back, 4xx doesn't) and reused it rather than inventing a rule.
  • Renamed it to a generic NetworkError.shouldFallBackToCache / BackendError.shouldFallBackToCache and pointed all callers (OfferingsManager + the error tests) at it directly. Behavior-preserving (the old offerings name only ever returned the same value).
  • Gated WorkflowManager.getWorkflowsList's failure-branch disk restore on error.shouldFallBackToCache.
  • Added two tests: 4xx skips restore (list + detail), 5xx restores.
  • Review follow-up (vegaro): the interim shouldFallBackToCachedOfferings delegating alias was redundant, so it was removed entirely instead of kept.

Human Decisions

  • Tackle this follow-up now; keep the change scoped to workflows + the shared error helper.

Key Implementation Decisions

  • Reuse the offerings classification via a generalized shouldFallBackToCache instead of a workflows-specific rule, so both paths treat backend errors consistently. Per review, the old shouldFallBackToCachedOfferings name was dropped (not kept as an alias) and callers use shouldFallBackToCache directly.
  • Gate the disk restore only; don't evict live in-memory state on a 4xx (matches how offerings scope their fallback).

Files / Symbols Touched

  • Sources/Networking/HTTPClient/NetworkError.swiftshouldFallBackToCachedOfferings renamed to shouldFallBackToCache.
  • Sources/Error Handling/BackendError.swift — same rename.
  • Sources/Purchasing/OfferingsManager.swift — uses shouldFallBackToCache.
  • Sources/Purchasing/WorkflowManager.swiftgetWorkflowsList failure branch guards restore on shouldFallBackToCache.
  • Tests/UnitTests/Purchasing/WorkflowManagerTests.swift — 4xx-no-restore + 5xx-restore tests.
  • Tests/UnitTests/Networking/{BackendError,NetworkError}Tests.swift — updated to shouldFallBackToCache.

Validation

  • swift build: clean.
  • swiftlint on changed files: no violations.
  • xcodebuild test -scheme UnitTests: BackendErrorTests 9, NetworkErrorTests 8, OfferingsManagerTests 39, WorkflowManagerTests 39 all passing (95 total, 0 failures) after the rename, iPhone 16 Pro sim.

Validation Gaps

  • Covers the manager-level decision deterministically; not an end-to-end device test of a live 4xx.

Review Focus

  • shouldFallBackToCache semantics match the prior shouldFallBackToCachedOfferings exactly (4xx → false, 5xx/no-status → true); offerings now call it directly, so no offerings behavior change.
  • Gate is on the disk restore only; in-memory state is left as-is on a 4xx.

Non-goals / Out of Scope

  • Evicting already-cached in-memory workflows on a 4xx.
  • Any offerings behavior change.

Omitted Context

  • Raw transcript, unrelated exploration, and chain-of-thought content omitted.

Note

Medium Risk
Changes offline/paywall behavior when /workflows returns 4xx and touches shared cache-fallback rules used by offerings, though offerings semantics are intended to stay the same.

Overview
Fixes workflows list fetch failures restoring stale disk cache on 4xx responses, which could show prefetched paywalls after the backend had explicitly rejected the request.

shouldFallBackToCachedOfferings is renamed and generalized to shouldFallBackToCache on NetworkError and BackendError (5xx / connection failures → allow cache fallback; 4xx → do not). OfferingsManager now gates disk fallback on the same property with unchanged semantics.

WorkflowManager.getWorkflowsList only runs list/detail disk restore when error.shouldFallBackToCache is true; on 4xx it skips restore and still calls onComplete. Unit tests cover 4xx vs 5xx restore behavior and completion.

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

The getWorkflowsList failure branch restored the cached list + details
on any error. That's right for transient errors (5xx / offline) but
wrong for a 4xx, where the backend is authoritatively rejecting the
request (workflows disabled, unauthorized), so we shouldn't serve stale
prefetched data.

Generalize the existing offerings classification into
BackendError/NetworkError.shouldFallBackToCache (the offerings property
now delegates to it, no behavior change) and gate the workflows disk
restore on it. 4xx skips the restore; 5xx and connection failures still
restore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella added the pr:fix A bug fix label Jun 8, 2026
@facumenzella facumenzella changed the title Don't serve stale workflows when the backend rejects the request fix(workflows): Don't serve stale workflows when the backend rejects the request Jun 8, 2026
@facumenzella facumenzella marked this pull request as ready for review June 8, 2026 17:26
@facumenzella facumenzella requested a review from a team as a code owner June 8, 2026 17:26
@vegaro

vegaro commented Jun 8, 2026

Copy link
Copy Markdown
Member

cool, I actually I am working on the Android counterpart here

@vegaro vegaro left a comment

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.

Opened a PR with some extra tests for better coverage #6948

Other than that, looks good

Comment thread Sources/Error Handling/BackendError.swift Outdated
…fix (#6948)

- testGetWorkflowsListCallsOnCompleteOnClientError: verifies onComplete()
  fires on 4xx even though disk restore is skipped (callers must not block)
- testGetWorkflowsListRestoresDetailsFromDiskOnServerError: verifies that
  on 5xx both list mapping AND workflow details are restored from disk, not
  just the list mapping

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@facumenzella

Copy link
Copy Markdown
Member Author

thanks @vegaro 👨‍🎤

Per review: shouldFallBackToCachedOfferings was only a thin delegate to
shouldFallBackToCache after the prior refactor. Remove it on NetworkError
and BackendError and point callers (OfferingsManager, tests) at
shouldFallBackToCache directly. Behavior-preserving.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella enabled auto-merge (squash) June 8, 2026 19:30
@facumenzella facumenzella merged commit f98f57f into main Jun 8, 2026
18 of 20 checks passed
@facumenzella facumenzella deleted the facu/workflow-list-4xx-no-stale branch June 8, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants