Skip to content

iOS: script download errors lose their message (Unknown error from a native module) #1448

Description

@yunsung-miso

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

  1. Serve a remote container or chunk over HTTPS.
  2. 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).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions