# Error library

> Errors I hit building my apps with Expo, React Native, iOS, npm, Supabase and Stripe, each with the exact error text, the cause and the fix that worked.

Web page: https://davidewins.com/errors

Errors I hit building my apps, with the fix that worked.

These are 63 problems from my own Expo, React Native, iOS and backend work. Each page has the exact error text (or the symptom, when there was no error), what caused it and what fixed it. A confirmed fix is one I saw work. A likely fix is the best answer I had, without full proof.

## Expo

- [Dev client red screen: No script URL provided (launcher never appears)](https://davidewins.com/errors/expo-dev-client-no-script-url-precompiled): SDK 56 links Expo's prebuilt XCFrameworks on EAS (local builds compile from source).
- [Bundle fails with Invalid expression encountered after supabase-js update](https://davidewins.com/errors/supabase-js-hermes-invalid-expression): supabase-js 2.90 and later include a dynamic import(/* webpackIgnore */ OTEL_PKG) that the Hermes v1 compiler cannot bundle.
- [After upgrading to SDK 57, expo-font / expo-image / expo-asset config is missing](https://davidewins.com/errors/expo-sdk57-plugins-must-be-declared): SDK 57 needs expo-asset, expo-font, expo-image, expo-sharing and expo-status-bar declared in the plugins array. expo install --fix cannot edit a dynamic app.config.ts, so it only prints the list.
- [SDK upgrade: export dies with SyntaxError after expo install --fix](https://davidewins.com/errors/expo-install-fix-leaves-babel-preset-stale): expo install --fix only reconciles Expo's tracked package set. It left babel-preset-expo (and jest-expo, @types/jest) on the previous major, which cannot parse the new native component specs.
- [expo-doctor wants TypeScript 6 but tsc reports hundreds of errors](https://davidewins.com/errors/typescript-6-explodes-typecheck-expo): TypeScript 6 changes ambient type resolution (jest globals and similar).
- [CI typecheck fails (or passes with a broken href) because .expo/types is missing](https://davidewins.com/errors/expo-typed-routes-ci-false-errors): Typed routes are generated into .expo/types/router.d.ts by the dev server, and .expo is gitignored.
- [Secret works in EAS builds but is wrong locally (401 in dev only)](https://davidewins.com/errors/expo-env-dollar-sign-expanded): Expo's .env loader expands $name inside values as variable references, so a secret containing $ loses characters locally.
- [Changed an EXPO_PUBLIC_ variable but the app still uses the old value](https://davidewins.com/errors/expo-public-env-needs-metro-clear): EXPO_PUBLIC_* values are inlined into the bundle at transform time and Metro caches transformed files, so a changed .env value is not picked up.
- [OTA update breaks Google sign-in (or changes the API URL) although the store build works](https://davidewins.com/errors/eas-update-ignores-eas-json-env): EAS builds apply the env block of the eas.json build profile, but eas update does not.
- [eas update export killed with SIGKILL on a small server](https://davidewins.com/errors/eas-update-oom-sigkill-small-box): The Metro export peaks during source-map serialisation. On a box with under 4 GB free the kernel OOM killer stops it regardless of --max-old-space-size.
- [OTA update crashes old installs after adding a native module](https://davidewins.com/errors/ota-bump-version-after-native-change): The JS references a native module (here WebRTC) that is not compiled into the installed binary.
- [expo export fails on the web bundle: Unable to resolve module mapbox-gl/dist/mapbox-gl.css](https://davidewins.com/errors/expo-web-bundle-mapbox-gl-css): npx expo export builds every platform in app config, including web. @rnmapbox/maps' web entry imports mapbox-gl CSS, which is not installed in a native-only app.
- [Google Sign-In works on your machine's builds and fails on CI/EAS builds](https://davidewins.com/errors/google-signin-web-client-id-undefined): GoogleSignin.configure() uses the web client ID, which existed only in a local .env.
- [Expo Router: find() returns undefined for a dynamic route id](https://davidewins.com/errors/expo-router-params-always-strings): Everything from the URL is a string. e.id === id compares the number 1 with the string "1" and fails.
- [Native tabs on iOS 26 ignore background colour props](https://davidewins.com/errors/expo-router-native-tabs-ignore-background): On iOS 26 the system tab bar is Liquid Glass and takes its colour from the content behind it.
- [Expo Router app opens on the second tab](https://davidewins.com/errors/expo-router-group-index-wrong-tab): Round-bracket groups do not appear in the URL, so (exercises)/index.tsx is also /, competing with the first tab's index.
- [Deep link or notification opens a screen with no back button](https://davidewins.com/errors/expo-router-deep-link-no-back-button): Nothing tells the stack which screen sits underneath when it is entered directly.
- [Opening a detail screen jumps to another tab](https://davidewins.com/errors/expo-router-shared-route-array-group): The detail route only exists in one tab's folder, so navigating to it switches to that tab.
- [router.setParams writes params onto the wrong screen after a tab switch](https://davidewins.com/errors/expo-router-setparams-wrong-screen): router.setParams changes whichever screen is focused. A search bar clearing itself on tab switch wrote ?q= onto the other tab.
- [Top tabs throw: Install the 'react-native-tab-view' package](https://davidewins.com/errors/expo-router-top-tabs-install-tab-view): Material top tabs need two extra packages, one of them native.
- [Link.AppleZoomTarget can only have a single native child (with one child)](https://davidewins.com/errors/expo-router-appzoomtarget-single-native-child): React Native flattens a plain View that only does layout, so nothing reaches the native side.
- [router.navigate from a modal to a shared route lands with no list underneath](https://davidewins.com/errors/expo-router-navigate-shared-route-from-modal): From a modal the router cannot tell which tab's copy of the shared route you mean, and without the anchor nothing is pushed beneath it.
- [Link.Preview / Link.Menu throws without Link.Trigger](https://davidewins.com/errors/expo-router-link-preview-needs-trigger): The preview and menu need to know which element is the press target.
- [Expo UI Slider renders squashed or with no width](https://davidewins.com/errors/expo-ui-slider-no-width): A SwiftUI slider has no intrinsic width. A Host matchContents sizes to its content, so it gives the slider nothing.
- [Expo UI Toggle ignores value / onValueChange](https://davidewins.com/errors/expo-ui-swiftui-prop-names): The SwiftUI components use SwiftUI's names: isOn / onIsOnChange for Toggle, selection / onSelectionChange for Picker.
- [Expo UI Picker renders options but never selects](https://davidewins.com/errors/expo-ui-picker-missing-tag): Each option needs a tag modifier so the picker knows which value it stands for.
- [SwiftUI component inside a React Native View inside a Host does not render](https://davidewins.com/errors/expo-ui-rn-view-inside-host): A React Native view inside a Host switches back to React Native layout, so SwiftUI below it needs its own Host.

## React Native

- [Screen crashes the whole app: Rendered more hooks than during the previous render](https://davidewins.com/errors/rules-of-hooks-usememo-after-early-return): A useMemo was added below the if (isLoading) return / if (error) return early returns.
- [Feed shows an error state for every address: ReferenceError before initialization](https://davidewins.com/errors/tdz-const-used-in-map-before-declaration): A .map() callback read a const declared a few lines further down. With an empty array the callback never runs, so tests and CI passed; with one item it threw inside the react-query queryFn and the screen showed its error state.
- [Screen crash: Maximum update depth exceeded](https://davidewins.com/errors/maximum-update-depth-competing-effects): Three separate effects each "corrected" the same value and overwrote each other, so every render triggered another setState.
- [Reanimated crash on device: Tried to synchronously call a non-worklet function on the UI thread](https://davidewins.com/errors/worklet-calls-non-worklet-helper): A useAnimatedStyle / useDerivedValue body called a plain module-level helper.
- [Rows wrapped in <Link asChild> collapse to a tiny height](https://davidewins.com/errors/link-aschild-function-style-dropped): Link's Slot merges the child's style with an object spread. The Pressable used a style function (style={({ pressed }) => ...}), and spreading a function gives {}, so the height was dropped.
- [Pressable buttons render invisible: style function fill never applied](https://davidewins.com/errors/uniwind-pressable-double-wrap-drops-style): uniwind already resolves react-native's Pressable to its own wrapper. Wrapping it again with withUniwind() dropped every style={({ pressed }) => ...} background.
- [iOS 26: large title invisible until you scroll](https://davidewins.com/errors/ios26-large-title-invisible-header-background): Setting headerStyle.backgroundColor with a large title hides the title on iOS 26 (noted in the native-stack source itself).
- [One screen's transparent header looks different from all the others](https://davidewins.com/errors/header-blur-effect-one-screen-differs): All screens spread the same shared header options, but one also set headerBlurEffect: "prominent".
- [formSheet opens with a header but a blank body](https://davidewins.com/errors/formsheet-inside-tab-stack-opens-blank): The formSheet screen was registered inside a tab's own stack (whose index mounts several panes).
- [reverseGeocodeAsync fails: Geocoding rate limit exceeded](https://davidewins.com/errors/expo-location-geocode-rate-limit): Apple's on-device geocoder has a tight per-app rate limit. Calling reverseGeocodeAsync on every provider mount (Fast Refresh, remounts, re-entering onboarding) hits it quickly in development.
- [Android: first location lookup fails right after sign-up](https://davidewins.com/errors/android-geocoder-refused-during-permission-dialog): expo-location pauses its geocoder while the app is in the background, and a system permission dialog counts as background.
- [Android Google Sign-In fails silently on the Play Store build but worked on direct APKs](https://davidewins.com/errors/google-signin-play-app-signing-sha1): Play App Signing re-signs .aab uploads with Play's own key. The Android OAuth client in Google Cloud only had the upload key's SHA-1, so Play-installed builds no longer matched.
- [App requests hang forever in development: The network connection was lost](https://davidewins.com/errors/phone-cannot-reach-tailscale-api-url): The API base URL pointed at a Tailscale address, but the phone was not on the tailnet.

## iOS

- [pod install fails: react-native-firebase SPM + static linkage is not supported](https://davidewins.com/errors/rnfirebase-spm-static-linkage): react-native-firebase defaults to pulling Firebase through Swift Package Manager.
- [iOS build fails in react-native-skia: 'third_party/base64.h' file not found](https://davidewins.com/errors/skia-base64-h-not-found-use-frameworks): Skia's relative include only resolves through the pod's header search paths.
- [Dev build fails codesigning: provisioning profile doesn't include the Apple Pay capability](https://davidewins.com/errors/apple-pay-entitlement-dev-bundle-id): The dev variant uses its own bundle ID (e.g. com.example.app.dev). The Stripe plugin requested the Apple Pay merchant entitlement for every variant, but only the production App ID had Apple Pay enabled in the developer portal.
- [EAS iOS build stops at signing: provisioning profile expired](https://davidewins.com/errors/eas-expired-provisioning-profile-noninteractive): The development provisioning profile expired. Renewing it needs an Apple login, which EAS cannot do in non-interactive mode (CI, scripts, agent shells).
- [Fresh SDK upgrade fails at pod install with a Mapbox clash, while a sibling app on the same SDK builds](https://davidewins.com/errors/mapbox-pod-install-after-fresh-expo-install): A fresh install pulled newer patch versions of Expo packages than the known-good app's lockfile, and the newer Expo modules clashed with Mapbox's prebuilt binaries.

## npm

- [EAS build fails at Install dependencies: Missing package from lock file](https://davidewins.com/errors/eas-npm-ci-missing-from-lock-file): EAS build images run npm 10, but the lockfile was written by npm 11. npm 11 leaves out some nested entries for optional or conflicting peers (here a nested peer of a chat SDK needing flash-list >=2.1.0) that npm 10's npm ci insists on.
- [EAS Install dependencies fails after adding a video SDK: deduped package below its peer range](https://davidewins.com/errors/eas-npm-ci-nested-peer-url-polyfill): The lockfile had react-native-url-polyfill deduped at 2.0.0 while the newly added video SDK peers ^3.0.0. npm accepted it locally but EAS's clean npm ci failed.
- [`npm ci | tail` reports exit 0 but the build still fails on install](https://davidewins.com/errors/pipe-exit-code-hides-npm-ci-failure): $? after a pipeline is the exit code of the last command (tail), not npm ci. tail almost always exits 0, so a failed install looks like a pass.
- [`npx -y typescript@x tsc --noEmit` prints no TS errors but did not typecheck at all](https://davidewins.com/errors/npx-typescript-could-not-determine-executable): The typescript package's bin is named tsc, not typescript, so npx cannot pick an executable.

## Supabase

- [Supabase .single() fails with PGRST116 for a row you know exists](https://davidewins.com/errors/postgrest-pgrst116-rls-hides-row): Row Level Security only had a read policy for the row's owner. A second role that also needed the row got zero rows back, which .single() reports as PGRST116, not a permission error.
- [PostgREST 500: canceling statement due to statement timeout on a jsonb filter](https://davidewins.com/errors/postgrest-jsonb-filter-statement-timeout): A deep jsonb path filter (.is('meta->a->b', null)) is evaluated for every row with no index, so it hits statement_timeout once the table grows.
- [Reversal runs twice when two requests race](https://davidewins.com/errors/idempotency-read-then-write-race): The idempotency guard read "already reversed?" and then inserted. Two callers could both pass the check before either wrote.

## Stripe

- [Stripe returns resource_missing for a PaymentIntent you can see in the dashboard](https://davidewins.com/errors/stripe-resource-missing-live-vs-test-key): The object was created in live mode and the API call used a test-mode secret key (or the reverse).
- [Failed Stripe transfers show as 0.00 in the admin log](https://davidewins.com/errors/stripe-failure-logged-as-zero): A catch-all failure logger was called without the amount, and the logger defaulted amount_cents || 0.

## Fly.io

- [Pushes stop deploying to Fly: build fails on a package that only exists on your machine](https://davidewins.com/errors/fly-deploy-fails-extraneous-local-package): A script under src/ imported a package that was installed locally but never added to package.json.
- [Fly auto-deploy from GitHub silently stops after renaming the GitHub owner](https://davidewins.com/errors/fly-github-autodeploy-stops-after-owner-rename): The repo moved to a new GitHub owner name. Pushes to main stopped triggering Fly deploys with no error anywhere; the last deploy was days old.

## TanStack

- [`npx tsr generate` wants to delete hundreds of files instead of generating TanStack routes](https://davidewins.com/errors/tsr-generate-wrong-package): tsr resolves to an unrelated dead-code remover package (ts-remove-unused), not TanStack's router CLI, because both ship a tsr bin.

## Other

- [Scheduled retry job runs every 6 hours and never does anything (401 every time)](https://davidewins.com/errors/cron-secret-posted-to-admin-only-route): The pg_cron job sent the cron shared secret to a route protected by admin-user auth, so every call returned 401 and was ignored.
- [eas-cli fails instantly with no route to host while dig shows a healthy IPv4 address](https://davidewins.com/errors/tailscale-magicdns-ipv6-only-eas-cli): Tailscale's MagicDNS returned only AAAA (IPv6) records through the system resolver and the Mac had no IPv6 route. dig queries DNS directly and showed IPv4, but curl, node and eas-cli use getaddrinfo and only got IPv6.
- [Web haptics do nothing on iPhone Safari (iOS 26)](https://davidewins.com/errors/ios-web-haptics-scripted-click-ignored): Safari has no navigator.vibrate. The fallback clicks a hidden <input type="checkbox" switch> from script, and iOS 26 ignores scripted clicks: only a real finger on a real switch produces a haptic.
- [adb connect fails after pairing over Wi-Fi](https://davidewins.com/errors/adb-wireless-pairing-port-vs-connect-port): The pairing screen shows one IP:port for adb pair; adb connect needs the different port shown under IP address & Port on the main Wireless debugging screen.
- [Time-limited offers expire instantly in local development](https://davidewins.com/errors/local-clock-skew-expires-offers): The Mac clock was 81 seconds slow. The local API set expires_at = now + 25 s by its own clock, which was already in the past by the database's clock.
- [Scraped pages have no ratings, but the browser shows them](https://davidewins.com/errors/scraped-ratings-null-pre-hydration-html): The pages render ratings client-side after hydration. The scraper saved pre-hydration HTML, where the rating fields are null or stubbed to 0.
