Environment
@callstack/repack 5.2.5 (also verified on @callstack/repack@5.3.0 and main)
- React Native 0.83.9, iOS 26.x, new architecture
Problem
On iOS, every transport-level script download failure reaches JavaScript as the generic message Unknown error from a native module. The underlying NSError description is dropped, so a timeout, a dropped connection, a TLS failure and a DNS failure are indistinguishable in production logs.
The cause is in packages/repack/ios/ScriptManager.mm — two call sites, in loadScript and prefetchScript:
[self downloadAndCache:config
completionHandler:^(NSError *error) {
if (error) {
reject(ScriptDownloadFailure, error.localizedFailureReason, nil);
} else {
NSURLError populates localizedDescription, but leaves localizedFailureReason nil. When the message passed to RCTPromiseRejectBlock is nil, React Native substitutes its own fallback string, so the original reason never reaches JS.
HTTP status failures are unaffected because downloadAndCache builds those errors itself and sets NSLocalizedFailureReasonErrorKey explicitly:
NSDictionary *userInfo = @{
NSLocalizedFailureReasonErrorKey : [NSString
stringWithFormat:@"Request should have returned with 200 HTTP status, but instead it received %ld", (long)statusCode]
};
That asymmetry is what makes the bug easy to miss: 403 failures log a useful message, while every network failure logs the generic one.
Android is not affected — RemoteScriptLoader.kt falls back with e.message ?: e.toString().
Reproduction
- Serve a remote container or chunk over HTTPS.
- Load it on a device with a degraded connection, or with Network Link Conditioner set to a profile slow enough to exceed the script
timeout (default 30s).
- Observe the rejection reaching JS.
Expected: a message identifying the failure, e.g. The request timed out. (NSURLErrorDomain -1001)
Actual: Unknown error from a native module
Impact
We hit this while investigating production reports of the app not opening. Datadog RUM showed 762 transport-level CDN failures over 7 days, and on iOS every one of them carried only the generic message, so we could not tell whether raising the timeout would help or whether we were looking at TLS or DNS problems instead.
Suggested fix
Fall back to localizedDescription and include the error domain and code, and pass the NSError through as the third argument so userInfo survives:
static NSString *DescribeScriptDownloadError(NSError *error)
{
NSString *reason = error.localizedFailureReason ?: error.localizedDescription;
if (reason.length == 0) {
reason = @"Script download failed";
}
return [NSString stringWithFormat:@"%@ (%@ %ld)", reason, error.domain, (long)error.code];
}
reject(ScriptDownloadFailure, DescribeScriptDownloadError(error), error);
The reject code stays ScriptDownloadFailure, so the retry gate in loadScriptWithRetry (LOADING_ERROR_CODES) keeps working unchanged.
Happy to open a PR with this if the approach looks right.
Environment
@callstack/repack5.2.5 (also verified on@callstack/repack@5.3.0andmain)Problem
On iOS, every transport-level script download failure reaches JavaScript as the generic message
Unknown error from a native module. The underlyingNSErrordescription is dropped, so a timeout, a dropped connection, a TLS failure and a DNS failure are indistinguishable in production logs.The cause is in
packages/repack/ios/ScriptManager.mm— two call sites, inloadScriptandprefetchScript:NSURLErrorpopulateslocalizedDescription, but leaveslocalizedFailureReasonnil. When the message passed toRCTPromiseRejectBlockisnil, React Native substitutes its own fallback string, so the original reason never reaches JS.HTTP status failures are unaffected because
downloadAndCachebuilds those errors itself and setsNSLocalizedFailureReasonErrorKeyexplicitly:That asymmetry is what makes the bug easy to miss:
403failures log a useful message, while every network failure logs the generic one.Android is not affected —
RemoteScriptLoader.ktfalls back withe.message ?: e.toString().Reproduction
timeout(default 30s).Expected: a message identifying the failure, e.g.
The request timed out. (NSURLErrorDomain -1001)Actual:
Unknown error from a native moduleImpact
We hit this while investigating production reports of the app not opening. Datadog RUM showed 762 transport-level CDN failures over 7 days, and on iOS every one of them carried only the generic message, so we could not tell whether raising the timeout would help or whether we were looking at TLS or DNS problems instead.
Suggested fix
Fall back to
localizedDescriptionand include the error domain and code, and pass theNSErrorthrough as the third argument souserInfosurvives:reject(ScriptDownloadFailure, DescribeScriptDownloadError(error), error);The reject code stays
ScriptDownloadFailure, so the retry gate inloadScriptWithRetry(LOADING_ERROR_CODES) keeps working unchanged.Happy to open a PR with this if the approach looks right.