Skip to content

[$250] Update required screen shown on latest iOS staging build after stale NewDot bundle sends outdated appversion #95258

Description

@mountiny

If you haven't already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.4.25-1 (native shell) / stale NewDot bundle 9.3.91-0
Reproducible in staging?: Needs Reproduction (Unable to reproduce — single isolated server-side event)
Reproducible in production?: Needs Reproduction
If this was caught during regression testing, add the test name, ID and link from BrowserStack:
Email or phone of affected tester (no customers): cole@expensify.com
Logs: VictoriaLogs investigation — request_id a143bbd4a9ef3335-SJC on 2026-07-01T07:35:23Z
Expensify/Expensify Issue URL:
Issue reported by: @vit (internal investigation)
Slack conversation: #Expensify Bugs — cole reported 2026-07-02 ~4:39 PM

Action Performed:

  1. Open the iOS native app on the latest TestFlight staging build
  2. Use the app normally (background/resume)
  3. Observe the full-screen Update required blocker

Expected Result:

Users on the latest staging build should never see the forced-update screen.

Actual Result:

cole@expensify.com saw the Update required / Please update to the latest version of New Expensify screen on iOS native TestFlight while already on the latest staging build. Hard-quitting the app cleared the screen and the app worked normally afterward.

Workaround:

Force-quit and reopen the app.

Platforms:

Select the officially supported platforms where the issue was reproduced:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Issue observed in the following devices

  • iOS (TestFlight staging)
  • Native shell: Expensify/9.4.25.1 CFNetwork/3860.600.12 Darwin/25.5.0
  • Screenshot attached in Slack thread (IMG_3948.PNG)

Screenshots/Videos

See Slack thread screenshot — full-screen Update Required blocker with green Update button.


Investigation summary (for implementers)

What triggered the screen

The forced-update UI is shown when any authenticated API returns jsonCode: 426:

  1. Backend (Web-Expensify/lib/Request.php) compares appversion param against MINIMUM_APP_VERSION (9.4.9 at time of incident)
  2. Frontend (App/src/libs/HttpUtils.ts) calls alertUser() → sets ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED
  3. App/src/Expensify.tsx throws CONST.ERROR.UPDATE_REQUIRED → error boundary renders App/src/pages/ErrorPage/UpdateRequiredView.tsx

The flag is RAM-only (App/src/setup/index.ts ramOnlyKeys) and is never cleared in-session — only a process restart resets it. That explains the self-recovery after hard-quit.

Server logs for cole@expensify.com (2026-07-01)

Exactly one request triggered the 426 across the entire Jun 15 – Jul 3 window:

Field Value
Timestamp 2026-07-01T07:35:23Z
request_id a143bbd4a9ef3335-SJC
command ReconnectApp
platform ios
referer ecash
appversion in request body 9.3.91-0
nginx User-Agent (native shell) Expensify/9.4.25.1
minimum at time 9.4.9
needsUpgrade true
jsonCode 426

Log sequence:

[ForceAppUpgrade] Comparing app version to minimum ~~ appVersion: '9.3.91-0' minimum: '9.4.9' needsUpgrade: '1'
Deprecated app version detected - forcing the user to upgrade
API threw ExpError ... exceptionJsonCode: '426'

All other cole requests that day used current versions (ecash9.4.24-0, ecash9.4.25-1, iphone9.4.25-1). The stale ecash9.3.91-0 source appeared once total.

Root cause hypothesis

The iOS HybridApp ran a stale NewDot JS bundle (9.3.91-0) inside a current native shell (9.4.25.1). The appversion sent on API calls comes from bundled package.json (App/src/libs/Network/enhanceParameters.tsappversion: pkg.version), so it reflected the old JS bundle, not the native binary version.

On app resume, ReconnectApp fired with the stale version → backend correctly returned 426 → client stuck on Update Required until restart loaded a fresh bundle.

Open question for implementer: How did HybridApp retain/serve bundle 9.3.91-0 after a TestFlight update to 9.4.25? Investigate bundle loading, OTA update, and cache invalidation in Mobile-Expensify / @expensify/react-native-hybrid-app.

What this is NOT

  • Not a backend false positive — version_compare('9.3.91-0', '9.4.9', '<') is correct
  • Not cole running an actually outdated build — native User-Agent confirms 9.4.25.1
  • Not related to the recent minimum-version bump logic being wrong — the stale bundle predates the bump

Proposed fix direction

  1. Investigate HybridApp bundle lifecycle — ensure NewDot JS bundle version always matches or is invalidated when native app updates (TestFlight/OTA)
  2. Add defensive logging — when native shell version ≠ bundled pkg.version, log/Sentry so future occurrences are diagnosable
  3. (Optional follow-up, separate issue) — auto-recover from transient 426 if subsequent requests succeed with current version

Key files

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mkhutornyi

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorOverdue

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
CRITICAL

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions