Introduction
When debugging React Native applications, you may expect the debugger to pause execution precisely where they place breakpoints within your code. However, a recurring issue has emerged where the debugger appears to stop on lines that the developer did not explicitly mark—especially within libraries or non-user code. This behavior undermines debugging precision and causes confusion and inefficiency during development.
What is the Problem?
The core problem is the appearance of so-called “ghost breakpoints”: these are debugger pauses on lines of code that were not marked by the user. These breakpoints usually appear in auto-generated or transpiled sections of the codebase, not in the original source files. This makes debugging asynchronous flows—especially those using async/await or Promises—particularly misleading and frustrating.
For example, when you place a breakpoint on an async function:
and you step over it, the debugger will take you to the asyncGenerator code:
What Causes the Problem?
The root of the issue lies in the transpilation process used in React Native applications, which are typically written in TypeScript or modern JavaScript and then converted (or transpiled) into plain JavaScript using tools like Babel.
How Transpilation Works
React Native codebases are usually written in TypeScript or modern JavaScript (ES6+), which includes syntax and features not natively supported in all JavaScript environments. Tools like Babel are used to transpile this code into plain JavaScript to ensure broad compatibility. This process involves:
- Transforming modern constructs like
async/await, arrow functions, classes, etc., into older JavaScript that can run in more environments. - Injecting polyfills for features like Promises or generators if the target runtime lacks native support.
- Converting
async/awaitinto generators using Babel plugins like@babel/plugin-transform-async-to-generator.
For instance, the following code:
// ts
async function fetchData() {
const response = await fetch('/data');
return response.json();
}
Is transpiled into something closer to:
// js
function fetchData() {
return regeneratorRuntime.async(function fetchData$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return fetch('/data');
case 2:
return _context.abrupt("return", _context.sent.json());
}
});
}
This transformation introduces additional wrapper functions, control flow logic, and calls to injected helpers like regeneratorRuntime. These elements do not correspond directly to lines in the original TypeScript or JavaScript source, complicating debugger mapping.
Where the Problem Originates
When debugging, the JS engine steps through the transpiled code, not the original source. While source maps are intended to map these positions back to your original TypeScript, this mapping breaks down with complex constructs like generators or async functions, especially across multiple Babel plugins and toolchains (e.g., Metro, Hermes).
The debugger relies on source maps to map transpiled code back to the original source code. However, when transformations like generator functions and Promise wrapping are involved, these maps can become inaccurate or ambiguous, especially in deeply nested or chained async logic. As a result, breakpoints may “drift” into these helper functions, creating the illusion of ghost breakpoints.
This is particularly problematic in the composed pipeline that uses Babel, Hermes, and other transformations. Known limitations in source maps with complex async constructs contribute heavily to this issue.
Known Issue in the React Native Community
This debugging behavior is widely known across various tools and platforms that make up the React Native development pipeline. Developers using Babel (especially with @babel/plugin-transform-async-to-generator and related plugins) have experienced challenges in maintaining accurate source mappings when debugging asynchronous code.
Hermes, the JavaScript engine, has made some progress on improved source map handling, but the problem persists.
Many discussions and GitHub issues reference this problem:
- Promise polyfill and async/await transpliation make it hard to debug · Issue #27966 · facebook/react-native · GitHub
- [Bug] Debugger stops at breakpoints that I didn't set on node_modules libraries, specially in Promises · Issue #2310 · microsoft/vscode-react-native · GitHub
- https://youtrack.jetbrains.com/issue/WEB-27680/Jest-Babel-Debugger-stops-on-a-wrong-line
- [Bug/Question] Breakpoint auto-relocate to other file in V1.78.0 · Issue #1715 · microsoft/vscode-js-debug · GitHub
- Debugger known issue on VSCode 1.78+ (vscode-js-debug 1.78+) and temporary workaround · Issue #1977 · microsoft/vscode-react-native · GitHub
- Babel Transpilation Discussion on async/await and generators
This post by Jörn Zaefferer summarized the state of the art of JS debugging in 2025:
Workarounds
There are a few known workarounds, though each comes with trade-offs:
- Disabling async-to-generator transformation: This can prevent complex rewrites, resulting in simpler source maps, but:
- May break compatibility with older JavaScript runtimes
- May not be viable depending on project setup
- Reduces the scope of optimization and transformation
- Can impact performance and compatibility, discouraged for production builds
- Debugging experience improvements are limited
- Manual debugging without async/await: Refactor code into
.then()chains—unrealistic for most codebases. - Source map tuning: Tools like Metro allow tweaking source map generation, but deep async logic remains difficult to map accurately. Furthermore, they are brittle and may break during bundling.
What is the community doing about it?
The broader JavaScript and React Native communities are actively working on this issue:
- Hermes is improving support for better source map fidelity and more accurate async stack traces. Hermes changelogs reflect ongoing enhancements in debugging.
- Metro bundler is undergoing active development to better handle source maps across complex async transforms.
- The React Native core team has acknowledged the pain around debugging and is working on initiatives like better integration with Chrome DevTools and improved error mapping with react-native-error-overlay.
- Babel is moving toward improving plugin modularity and performance, and async handling is on the roadmap for several tools involved in the transpilation pipeline.
There is no definitive fix today, but potential deprecation of certain Babel transforms offer hope for more accurate and stable debugging in future versions of React Native.
Recommendations
As mentioned before there is no definitive solution to this issue, and the available workarounds are not generic, it depends on the project specific. If you are facing the problem, verify on the above cited discussion if you can apply the proposed solutions.
But keep in mind that most of them will impact the performance of your application and they should not be apply for production builds, and do not apply them without making an analyzes of the impact.

