App crashes on startup with: [Worklets] Tried to synchronously call a non-worklet function addListener on the UI thread when upgrading to Expo sdk 55. #9023
Replies: 7 comments 2 replies
|
Hi @miguelartazos. I don't think that I'm strongly against the global singleton - the point of |
|
Hi Tomasz, thank you so much for your response. My Metro configuration is
standard Expo SDK 55 + Sentry + Uniwind — no custom resolver modifications
that would affect
.native.ts resolution. The platforms config is set correctly by Expo.
The issue isn't that Metro picks the wrong file systematically. It's that
the library ships a NOOP .ts alongside a real
.native.ts, and any Metro cache/fast-refresh edge case that loads both
creates a silent cache coherency failure → crash. The
NOOP's set() being a no-op and get() returning null! means there's zero
fault tolerance.
The global singleton via globalThis ensures both files share the same
WeakMap regardless of which gets loaded. This isn't an
anti-pattern — it's a defensive fix for a library design that assumes
Metro will never resolve the wrong file, which isn't
guaranteed.
Do you have any idea of what else could be causing this error? Thank you
again this issue has been annoying me for the past 2 days.
…On Fri, Feb 27, 2026 at 12:25 PM Tomasz Żelawski ***@***.***> wrote:
Hi @miguelartazos <https://github.com/miguelartazos>. I don't think that
serializableMappingCache.ts should ever be picked in favor of
serializableMappingCache.native.ts during native development - it seems
like a problem with your Metro/Expo configuration.
I'm strongly against the global singleton - the point of .native.ts and
.ts implementations are to prevent such anti-patterns in the codebase and
ensure proper encapsulation.
—
Reply to this email directly, view it on GitHub
<#9023 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BUCMS75QWIVNFLPKDBHWEUL4OCDZ3AVCNFSM6AAAAACWBUDF3KVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTKOJUHE4TANQ>
.
You are receiving this because you were mentioned.Message ID:
<software-mansion/react-native-reanimated/repo-discussions/9023/comments/15949906
@github.com>
|
|
I'm also running into the same issue here but the op's workaround couldn't fix mine. I've been migrating my bare react native app to expo 55 and struggling with this one for a week. One thing I did notice here is these lines in metro.config.js
if I use getDefaultConfig from expo/metro-config, I'll get the error, while using getDefaultConfig from @react-native/metro-config won't face the issue. But doing so will compromise my intent to migrate fully to expo. |
|
hey, I don't know if you OP here has react-native-dotenv installed in your project, but replacing react-native-dotenv by react-native- config fixed the issue for me. |
|
I fixed this issue when removed react-native-dotenv library from my project |
✅ UPDATE — found the trigger in our case (and a fix)Follow-up with what resolved it for us. The culprit was a second Babel plugin in the Metro transform pipeline: This lines up with the reports here that switching TL;DR for anyone hitting this: check It still looks like an underlying worklets fragility (a benign-looking Babel plugin shouldn't be able to break shared-value serialization), so a proper upstream fix would be making that serialization robust to transform-pipeline ordering — but the above is a reliable workaround. Original report (environment, crash stack, things ruled out)Still reproducing on Expo SDK 55 / RN 0.83.6 — and fatal in ReleaseHitting the same family on a bare Expo SDK 55 workflow: RN 0.83.6, React 19.2, Dev: the app boots past worklets init and reaches app render, but logs ~85× (non‑fatal LogBox): Release / production: the same condition is fatal. The app runs ~6–15s, then i.e. a worklet scheduled on the UI runtime throws the RemoteFunction guard and, with no LogBox in Release, it terminates the process. Things we tried — all still crash:
Not an app‑usage issue: no module‑scope shared values, idiomatic The signature matches the OP's diagnosis (shared values reaching the UI runtime serialized field‑by‑field, so their listener methods become RemoteFunction stubs), but it is fatal in Release rather than just a dev warning. Happy to share the full |
Uh oh!
There was an error while loading. Please reload this page.
Environment
Root cause (investigation)
We traced the crash to
createMapperRegistry().start()inmappers.tscallingsv.addListener()on shared values that were serialized field-by-field instead of via the__init→legacy_makeMutableUIpath. As a result,addListenerbecomes a RemoteFunction stub and throws when called on the UI thread.Hypothesis:
serializableMappingCacheis split between two implementations:serializableMappingCache.ts— NOOP stub (setdoes nothing,getreturns null)serializableMappingCache.native.ts— real WeakMapIf Metro resolves Reanimated’s
mutables.tsto the NOOP cache and Worklets’serializable.native.tsto the real cache, then:makeMutableNativewrites to the NOOP cache (no-op)createSerializablereads from the real cache (empty)clonePlainJSObject→addListenerbecomes RemoteFunction → crashDiagnostic evidence:
sv.addListenerhasisRemote: trueat the crash sitecreateSerializableran beforemutables.tsloaded in our tests (load order)Workaround
Using a global singleton for the cache fixes the crash:
// serializableMappingCache.native.ts and serializableMappingCache.ts
const cache = ((globalThis as any).__workletsSerializableCache ??= new WeakMap());
All reactions