You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Applications embedding Babylon Native depend on field crash reports for ongoing maintenance, and today a fetch()/XMLHttpRequest failure arrives there with no usable signal: DNS failure, connection refused, TLS rejection, proxy auth failure, server outage, and a missing bundled app:/// asset are all byte-identical, and an un-caught rejection never reaches the host (BN) application at all. This issue tracks the chain of fixes; each hop is independently shippable.
The chain (where fidelity is lost today)
UrlLib swallows transport errors at the source. ✅ Fixed by Expose normalized transport-error detail (ErrorString/ErrorSymbol/ErrorCode) UrlLib#31 (open): the Apple backend discarded the NSError (long-standing in-code TODO) and the curl backend caught and dropped its own failure, leaving only StatusCode() == 0. UrlLib now exposes ErrorString() / ErrorSymbol() / ErrorCode(), normalized as "<domain>:<symbol>(<code>): <detail>" (e.g. curl:CURLE_COULDNT_RESOLVE_HOST(6): ..., nsurl:NSURLErrorCannotConnectToHost(-1004): ..., urllib:AppResourceNotFound(0): ...) — stable tokens for observability-pipeline filtering, purely additive (status-0 contract unchanged).
The fetch polyfill flattens every transport failure to a constant string.Fetch.cpp throws std::runtime_error{"fetch: network request failed"}, discarding result.error() and the URL/method context it has in scope; the rejection surfaces as a plain Error (browsers/Node/Bun: TypeError) with no cause, no code, and a .stack snapshotted inside the scheduler tick — i.e. zero user frames. Proposed shape, once the UrlLib pin includes Test CI #31:
reject with a TypeError whose message is stable ("fetch failed" style — keeps crash-report grouping intact), carrying the variable detail as properties: cause (message from ErrorString()), code (ErrorSymbol()), url;
capture the JS call-site stack synchronously inside fetch() before SendAsync() (create the rejection Error, or a stack carrier, while user frames are still on the stack — the undici approach) so crash reports can attribute the failing call;
same treatment for XMLHttpRequest's error path.
Unhandled promise rejections never reach UnhandledExceptionHandler.AppRuntime::Dispatch only catches synchronous Napi::Error throws from dispatched callbacks; no engine-level rejection tracker is wired anywhere in Core, so a fire-and-forget fetch() failure (or a throw inside any .then) vanishes silently — the embedder's handler never fires and the process exits 0. Proposal: an opt-in AppRuntime::Options handler (or routing into the existing one) fed per engine — Isolate::SetPromiseRejectCallback (V8), JsSetHostPromiseRejectionTracker (Chakra), JSGlobalContextSetUnhandledRejectionCallback (JavaScriptCore), and the JSI tracker — the per-engine seams already exist as AppRuntime_{V8,Chakra,JavaScriptCore,JSI}.cpp.
fetch ignores init.signal. The implementation passes arcana::cancellation::none() for both continuations, so abort never rejects (AbortError) and never cancels the transport. Two prerequisites: UrlRequest::Abort() currently only cancels the Windows backend (the curl/NSURLSession backends never observe m_cancellationSource), and the AbortSignal polyfill predates the modern spec (no reason/throwIfAborted(), writable aborted). Worth sequencing with Playground: link AbortController + TextEncoder polyfills from JsRuntimeHost BabylonNative#1708, which installs AbortController globally in the Playground — after which library feature-detection passes while signals silently no-op, and user-initiated cancellations become indistinguishable from network failures in telemetry.
Validation
Beyond unit tests per hop (UrlLib#31 already ships offline-deterministic transport-failure tests + CI), the highest-value conformance ports for this area: the WPT fetch/api/abort/general.any.js core cases (pre-aborted/mid-flight/post-settle, signal.reason), undici's fetch failed-with-cause assertions from test/fetch/client-fetch.js, a four-way failure-shape bank (refused / NXDOMAIN / bad TLS / missing local asset must be distinguishable and TypeError-shaped), and a native-side test that an unhandled rejection actually reaches the host handler.
Happy to submit PRs for hops 2–4 individually (2 is unblocked as soon as the UrlLib pin can include #31; 3 and 4 are independent).
Applications embedding Babylon Native depend on field crash reports for ongoing maintenance, and today a
fetch()/XMLHttpRequestfailure arrives there with no usable signal: DNS failure, connection refused, TLS rejection, proxy auth failure, server outage, and a missing bundledapp:///asset are all byte-identical, and an un-caught rejection never reaches the host (BN) application at all. This issue tracks the chain of fixes; each hop is independently shippable.The chain (where fidelity is lost today)
UrlLib swallows transport errors at the source.✅ Fixed by Expose normalized transport-error detail (ErrorString/ErrorSymbol/ErrorCode) UrlLib#31 (open): the Apple backend discarded theNSError(long-standing in-code TODO) and the curl backend caught and dropped its own failure, leaving onlyStatusCode() == 0. UrlLib now exposesErrorString()/ErrorSymbol()/ErrorCode(), normalized as"<domain>:<symbol>(<code>): <detail>"(e.g.curl:CURLE_COULDNT_RESOLVE_HOST(6): ...,nsurl:NSURLErrorCannotConnectToHost(-1004): ...,urllib:AppResourceNotFound(0): ...) — stable tokens for observability-pipeline filtering, purely additive (status-0 contract unchanged).The fetch polyfill flattens every transport failure to a constant string.
Fetch.cppthrowsstd::runtime_error{"fetch: network request failed"}, discardingresult.error()and the URL/method context it has in scope; the rejection surfaces as a plainError(browsers/Node/Bun:TypeError) with nocause, nocode, and a.stacksnapshotted inside the scheduler tick — i.e. zero user frames. Proposed shape, once the UrlLib pin includes Test CI #31:TypeErrorwhose message is stable ("fetch failed"style — keeps crash-report grouping intact), carrying the variable detail as properties:cause(message fromErrorString()),code(ErrorSymbol()),url;fetch()beforeSendAsync()(create the rejection Error, or a stack carrier, while user frames are still on the stack — the undici approach) so crash reports can attribute the failing call;XMLHttpRequest's error path.Unhandled promise rejections never reach
UnhandledExceptionHandler.AppRuntime::Dispatchonly catches synchronousNapi::Errorthrows from dispatched callbacks; no engine-level rejection tracker is wired anywhere in Core, so a fire-and-forgetfetch()failure (or a throw inside any.then) vanishes silently — the embedder's handler never fires and the process exits 0. Proposal: an opt-inAppRuntime::Optionshandler (or routing into the existing one) fed per engine —Isolate::SetPromiseRejectCallback(V8),JsSetHostPromiseRejectionTracker(Chakra),JSGlobalContextSetUnhandledRejectionCallback(JavaScriptCore), and the JSI tracker — the per-engine seams already exist asAppRuntime_{V8,Chakra,JavaScriptCore,JSI}.cpp.fetchignoresinit.signal. The implementation passesarcana::cancellation::none()for both continuations, so abort never rejects (AbortError) and never cancels the transport. Two prerequisites:UrlRequest::Abort()currently only cancels the Windows backend (the curl/NSURLSession backends never observem_cancellationSource), and theAbortSignalpolyfill predates the modern spec (noreason/throwIfAborted(), writableaborted). Worth sequencing with Playground: link AbortController + TextEncoder polyfills from JsRuntimeHost BabylonNative#1708, which installsAbortControllerglobally in the Playground — after which library feature-detection passes while signals silently no-op, and user-initiated cancellations become indistinguishable from network failures in telemetry.Validation
Beyond unit tests per hop (UrlLib#31 already ships offline-deterministic transport-failure tests + CI), the highest-value conformance ports for this area: the WPT
fetch/api/abort/general.any.jscore cases (pre-aborted/mid-flight/post-settle,signal.reason), undici'sfetch failed-with-causeassertions fromtest/fetch/client-fetch.js, a four-way failure-shape bank (refused / NXDOMAIN / bad TLS / missing local asset must be distinguishable andTypeError-shaped), and a native-side test that an unhandled rejection actually reaches the host handler.Happy to submit PRs for hops 2–4 individually (2 is unblocked as soon as the UrlLib pin can include #31; 3 and 4 are independent).
Related: #188 (fetch polyfill), #195 (statusText — same UrlLib-consumption pattern hop 2 would follow), BabylonJS/BabylonNative#1707.