A stack trace names the file and the line, until a release build turns it into p@1:132161.
A stack trace is the list of calls your app had made at the moment it failed, newest call at the top. In development each frame names a file and a line inside it, so reading one means starting at the top, going down to the first file that belongs to you, and opening it at that line. Expo's own documentation calls the trace extremely valuable for that reason: it gives you the location of the error. What nobody warns you about is that the same crash in a release build arrives as something like p@1:132161, because your JavaScript was minified and compiled to bytecode on the way into the app. A release build is the kind you make when publishing to the App Store, which is where most people meet this for the first time.
Everything below was read from React Native's and Expo's own documentation on 17 October 2026, with each page's own last-updated date next to anything that moves. What follows is how to read the trace you have now, what the release build takes away, and the two commands that put the names back. Then the detail that wastes the most time: a source map only works against the exact commit the build came from.
Start hereThe short version
Read down to the first file that is yours then check which build you are in.
In development the trace is already readable, and the work is reading it rather than decoding it. The error message says what broke, the top frame says where, and the frames below it walk back through the calls that got there. Anything inside node_modules is a dependency running your code, so keep going until a path looks familiar.
In a release build none of that is true. Function names are gone, line numbers are bytecode offsets, and the in-app tools that would have shown you the error are switched off. Getting a readable trace out of a release build is a separate job with its own tool, and the thing it needs most is a source map that matches the build exactly.
What is the trace in front of you actually saying?
Expo documents the development case with a worked example, and it is worth copying the method rather than the example. An error appears, you get the message and a stack trace, and the trace shows up in two places at once: in your terminal and on the device, in Expo Go or in a development build. Expo's example error points at HomeScreen.js, line 7. Open that file at that line and a variable called renderDescription is being referenced that was never declared. The message and the location together finish the job. That is the shape of most development errors, and the discipline is to read both before doing anything else.
The red screen has a name and so does the yellow one. A Redbox error shows when a fatal error prevents your app from running. A Yellowbox warning tells you there is a possible issue you should probably fix before you ship. Both are drawn by LogBox, which is part of React Native rather than part of Expo. React Native's own debugging page, last updated 14 October 2026, adds the detail that explains the behaviour: on an unrecoverable error such as a syntax error, LogBox opens at the location of the error and cannot be dismissed, because your code cannot run at all. Fix the syntax and it clears itself, either through Fast Refresh or after a manual reload.
Console errors and warnings behave differently from fatal ones. Errors appear as an on-screen notification with a red badge and a count, and tapping it opens an expanded view you can page through. Warnings show a banner with no details and point you at React Native DevTools instead. Two settings can make an error vanish without anything being fixed. While React Native DevTools is open, every error except a fatal one is hidden from LogBox, and React Native recommends treating the DevTools Console panel as the source of truth for exactly that reason. On top of that, LogBox can be silenced in code, either entirely or for specific messages and regular expressions, and it treats some React errors as warnings so they never show as an in-app error notification at all.
So if a project you did not write stopped showing you red screens, check for that before you believe the app is healthy. A silenced warning filter and a fixed bug look identical from the outside.
Expo, errors and warnings, page dated 22 July 2026, read 17 October 2026
Check yours
Which frame are you looking at?
Pick the shape that matches the line on your screen. The shapes come from React Native's and Expo's own examples.
What it is: A minified function name and a bytecode offset, from a release bundle. React Native prints exactly this shape in its own example.
What to do: You need the source map from that build and metro-symbolicate. Nothing you can read on the device will help.
Shapes, not rulings. Your bundler and engine settings decide what your own frames look like.
Why does the same crash come back as p@1:132161?
React Native says it plainly. If an app throws an unhandled exception in a release build, the output may be obfuscated and hard to read. The example on its own page is an Android log showing a fatal exception on a thread called mqt_native_modules, a JavascriptException reading Failed, js engine: hermes, stack, and then four frames: p@1:132161, p@1:132084, f@1:131854 and anonymous@1:131119. Those entries are minified function names and bytecode offsets. The goal, in React Native's words, is to translate them into file, line and function name, something closer to App.js:54:initializeMap. React Native calls that symbolication.
Two separate things did this to your trace. The first is minification, which Expo documents as running on JavaScript during the production export, when commands like npx expo export or eas build run. Expo describes it as removing unnecessary characters, collapsing whitespace and stripping comments, and React Native describes what survives as minified function names, which is where p and f came from. The second is the engine. Hermes is the default JavaScript engine in an Expo project and it compiles JavaScript to bytecode ahead of time to cut start-up time. That is why the number after the colon is an offset into bytecode rather than a line in a file you could open. Neither step is a mistake and neither is worth turning off. They are what makes the shipped app start quickly.
The same release build also removes the tools you would normally read the error with. React Native states that debugging features such as the Dev Menu, LogBox and React Native DevTools are disabled in release builds. There is no red screen to tap, no console drawer and no notification count. Whatever the app managed to write to the device log, or to a reporting service you wired in beforehand, is the whole of what you get. Expo's error reporting documentation, changed on 16 October 2026, puts the same problem in bundle terms: in a production app your JavaScript is bundled and minified, so stack traces point at line and column positions in the generated bundle rather than in your source files, and a frame with no map behind it reads like index.android.bundle:1:481231.
This is a property of compiled mobile stacks rather than a React Native defect, and it is one of the quieter things to weigh when you set Flutter against React Native. What matters for the stack you already have is that the fix is known, written down and a one-time change to how you build.
React Native, debugging release builds, last updated 12 August 2026, read 17 October 2026
How do you turn a minified trace back into your own file names?
With a source map, and the first thing to know is that the two platforms do not start from the same place. React Native documents that source maps are required to symbolicate stack traces, and then states that on Android source maps are enabled by default while on iOS they are disabled by default. That single asymmetry explains a great deal of confusion, because the Android trace from a tester often symbolicates on the first try and the iOS one from the same release does not. On Android the relevant setting is a hermesFlags entry including -output-source-map in android/app/build.gradle. On iOS you edit the build phase named Bundle React Native code and images in Xcode and add a SOURCEMAP_FILE export above the other exports. Either way Metro prints the source map output path in the build output, which is how you confirm it took effect rather than hoping.
The tool is metro-symbolicate, and it reads a trace on standard input. Point it at the map file and redirect a text file of the trace into it, or on Android pipe the log straight through by running adb logcat with the dump flag into npx metro-symbolicate with the map path. Running it with no arguments prints the usage. Three notes on React Native's page are each worth the time they save. A build may generate several source maps, and you want the one at the path shown in the documented examples. The map must correspond to the exact commit of the crashing app, because small changes in source code cause large differences in offsets. And if metro-symbolicate exits immediately reporting success, the input came from a terminal instead of a pipe or a redirect, which looks like a tool that works and produces nothing.
If your builds run on Expo Application Services there is a managed route that skips the file handling. Expo documents setting uploadSourceMaps to true in a build profile in eas.json, after which EAS Build stores the source map produced when your JavaScript is bundled and symbolication works for every error reported from that build, with no change to your app code. The constraints are written down too: it needs EAS CLI version 22.0.0 or later, it only applies to builds that run on EAS Build servers, and a local build does not upload one. For an over the air update the equivalent is an upload source maps flag on eas update, which needs EAS CLI version 24.9.0 or later. The embedded copy of your source is stripped from the map before upload, so only file names and position mappings are stored. Those figures were read on 17 October 2026 from a page last changed the day before.
The exact commit rule is the one that bites hardest on a project you did not type, because the tree on your machine moves faster than the builds you shipped. Keep the map with the build, or keep a way to check out the commit the build came from. A map from yesterday against a bundle from last week produces confident, wrong file names, which is worse than no names at all.
React Native, enabling source maps and using metro-symbolicate, read 17 October 2026
Can you make a release build fail on your own desk?
Mostly yes, and it is the cheapest move available. Expo's debugging runtime issues page documents a production mode for the local dev server: start the project with the no dev and minify flags together. The no dev flag tells the server to run in production mode, and the minify flag minifies your code the same way it is minified for a production JavaScript bundle. Expo's own description of what that buys you is the useful part: running your app in production mode locally will show errors that normally would not be thrown. A release-only bug then happens on a machine where you still have your editor, your terminal and a debugger, instead of on a tester's phone.
There is a second way a release build changes what you can see, and it is a setting rather than a side effect. Expo's minification page documents a drop_console option in the minifier config that removes all console log statements from a production build, and a variant that takes a list, so a project can drop log and info while keeping warn and error. If someone configured that and you are waiting for a console.log to show up in a release build, it is never going to arrive. Check the Metro config before you conclude the code never ran.
For the errors whose message is too cryptic to act on, Expo's advice is unglamorous and correct. Read the stack trace first. Then isolate the code that is throwing, by going back to a version that worked and reapplying your recent changes piece by piece until it breaks again. Use breakpoints, or a console.log, to confirm that a line actually runs and that a variable holds what you assumed. On a generated project that loop doubles as the fastest tour of a codebase you have never read, because you end up stepping through the parts that matter instead of scrolling the parts that do not.
Set aside real time for the first pass. It costs time rather than money, and the money side of shipping is set out in what it costs to publish an app.
The same crash, seen from each kind of build
| What you get | Development build | Release build |
|---|---|---|
| A red screen on the error itself | Yes | No |
| The frame names your file and line | Yes | Only with a source map |
| Your console.log output | Yes | Gone if drop_console is set |
| A fatal error clears itself on save | Yes | No |
| Where the trace arrives | Terminal and on the device | Device log, or your reporting dashboard |
| What you need to read it | Nothing | The source map from that exact build |
Where this lands if an agent generated the project
Nothing about reading a trace changes because an agent wrote the code. One thing about your position does: the file named in the top frame is a file you have never opened. That is uncomfortable the first time and then it stops being a problem, because the trace is the one piece of documentation in the project that is guaranteed to be accurate about where the code went.
Newly is an AI app builder. You describe an app in plain English and it writes a real React Native and Expo project that you own, which matters here in a specific way: the path in the frame is a real path on disk and the line exists in a file you can open. Two-way GitHub sync is the part that serves the exact commit rule, because the commit a build came from sits in a repository you control, so you can check it out and symbolicate against a map that matches rather than against today's working tree. It is $25 a month and there is no free plan.
The reason to set source maps up early is what Newly actually ships. It uploads iOS builds to TestFlight, publishes to Google Play internal testing and builds a standalone Android APK. All three are release builds. So the first trace a tester sends you is the minified kind, not the readable kind, and iOS is the platform where source maps are off until you turn them on. Newly does not press submit on the App Store for you, because Apple requires the content owner to submit the app, which means the person reading that trace is you.
The honest limit: no builder reads the trace for you. It can write the project, run it and ship it, and the frames will still name files you have to open and judge. That is also the answer to a question people ask in the same breath, which is who owns AI generated code. You do, including the bugs.
Questions people ask about reading a stack trace
Start at the top and read down until you reach a file that belongs to you. A trace is a report of the recent calls your application made when it crashed, so the newest call is at the top and the error message above it says what went wrong. In development each frame names a file and a line, which is why Expo's documentation calls the trace extremely valuable: it hands you the location. Frames inside node_modules are your dependencies running your code, so keep going past them. The first path you recognise is where to open your editor.
Turn source maps on before you need them
Check the iOS setting, check that Metro prints a map path in the build output, and keep the map with the build. Then the first trace a tester sends you is a file and a line instead of a puzzle.
Start building