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
The same transport behaviour is still present on master as of 2026-08-29.
Operating System
Android (experimental), built on macOS arm64 and reproduced on an Android 15 arm64 emulator.
Description
The Android JavascriptInterface binding transport discards the structured CallError produced for a bound Go method. In particular, an application's ServiceOptions.MarshalError payload is correctly stored in CallError.Cause, but Android reduces the wrapped error to err.Error() and the JavaScript runtime reconstructs only a plain Error.
This makes Android binding semantics differ from the HTTP transport used on other platforms: callers cannot inspect error.cause, so structured application errors and session-routing metadata are lost.
The loss occurs in two places:
pkg/application/application_android.go calls fail(err.Error()) in handleRuntimeCallForAndroid, returning only {"ok":false,"error":"..."}.
The Android branch of @wailsio/runtime rejects with new Error(envelope.error), without restoring kind, message, or cause from a CallError.
This is distinct from #5723 / #5858. Those address non-JSON responses in the Android HTTP fallback path and intentionally wrap the error as a string matching the existing JavascriptInterface envelope. They do not preserve CallError.Cause.
Error
Binding call failed: Bound method returned an error: login: Please log in first.
undefined
The custom structured payload was present in the original CallError.Cause, but is no longer available to JavaScript.
Expected behaviour
Android should preserve the same bound-method error semantics as the standard HTTP transport:
return the complete CallError (kind, message, and cause) in the Android response envelope;
reconstruct the corresponding JavaScript error, including error.cause;
preserve arbitrary JSON returned by ServiceOptions.MarshalError.
For backward compatibility, transport/internal failures could continue accepting a string error, while bound CallError values use a structured error object.
Screenshots
Not applicable.
Attempted Fixes
Confirmed that MarshalError is invoked and that bindings.go assigns its JSON to CallError.Cause.
Confirmed that the Android loss happens after HandleRuntimeCallWithIDs returns the wrapped CallError.
Checked current master; it still serializes only err.Error() and creates a plain JavaScript Error.
Application-side parsing of the final message can recover a textual prefix such as login, but cannot recover arbitrary structured fields and is therefore not an equivalent workaround.
System Details
Wails CLI | v3.0.0-beta.15
Go Version | go1.27.0
Host OS | macOS 26.5.2 (25F84), arm64, Apple Silicon
Android SDK | /opt/homebrew/share/android-commandlinetools
Android NDK | 26.3.11579264
Java | OpenJDK 21.0.12
Emulator | Android 15, Google APIs, arm64-v8a
npm | 11.16.0
wails3 doctor reports no issues.
Additional context
A complete fix likely needs both sides of the Android transport to change:
Go: detect the wrapped *application.CallError (for example with errors.As) and encode it in the Android envelope instead of calling only err.Error().
JavaScript runtime: when envelope.error is structured, construct ReferenceError, TypeError, or RuntimeError consistently with the HTTP transport and assign its cause; continue supporting legacy string envelopes.
Regression coverage should include a bound method using a custom MarshalError and assert that an Android runtime rejection exposes the original structured cause.
Wails version family
v3
Exact Wails version
v3.0.0-beta.15
The same transport behaviour is still present on
masteras of 2026-08-29.Operating System
Android (experimental), built on macOS arm64 and reproduced on an Android 15 arm64 emulator.
Description
The Android
JavascriptInterfacebinding transport discards the structuredCallErrorproduced for a bound Go method. In particular, an application'sServiceOptions.MarshalErrorpayload is correctly stored inCallError.Cause, but Android reduces the wrapped error toerr.Error()and the JavaScript runtime reconstructs only a plainError.This makes Android binding semantics differ from the HTTP transport used on other platforms: callers cannot inspect
error.cause, so structured application errors and session-routing metadata are lost.The loss occurs in two places:
pkg/application/application_android.gocallsfail(err.Error())inhandleRuntimeCallForAndroid, returning only{"ok":false,"error":"..."}.@wailsio/runtimerejects withnew Error(envelope.error), without restoringkind,message, orcausefrom aCallError.This is distinct from #5723 / #5858. Those address non-JSON responses in the Android HTTP fallback path and intentionally wrap the error as a string matching the existing
JavascriptInterfaceenvelope. They do not preserveCallError.Cause.To Reproduce
Register a service with a custom error marshaler:
Call the generated binding and inspect the rejection:
On Android, the result is equivalent to:
The custom structured payload was present in the original
CallError.Cause, but is no longer available to JavaScript.Expected behaviour
Android should preserve the same bound-method error semantics as the standard HTTP transport:
CallError(kind,message, andcause) in the Android response envelope;error.cause;ServiceOptions.MarshalError.For backward compatibility, transport/internal failures could continue accepting a string error, while bound
CallErrorvalues use a structured error object.Screenshots
Not applicable.
Attempted Fixes
MarshalErroris invoked and thatbindings.goassigns its JSON toCallError.Cause.HandleRuntimeCallWithIDsreturns the wrappedCallError.master; it still serializes onlyerr.Error()and creates a plain JavaScriptError.login, but cannot recover arbitrary structured fields and is therefore not an equivalent workaround.System Details
wails3 doctorreports no issues.Additional context
A complete fix likely needs both sides of the Android transport to change:
*application.CallError(for example witherrors.As) and encode it in the Android envelope instead of calling onlyerr.Error().envelope.erroris structured, constructReferenceError,TypeError, orRuntimeErrorconsistently with the HTTP transport and assign itscause; continue supporting legacy string envelopes.Regression coverage should include a bound method using a custom
MarshalErrorand assert that an Android runtime rejection exposes the original structured cause.