wailsapp/wails · error · TypeError
CancellablePromise does not support transparent subclassing.
Error message
CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.
What it means
SetBkMode sets how GDI draws text/background hatching — TRANSPARENT or OPAQUE — and returns the previous mode. The w32 wrapper panics with 'SetBkMode failed' when the API returns 0, which happens when the mode argument is neither TRANSPARENT(1) nor OPAQUE(2), or the HDC is invalid. Because 0 can also be a legitimate 'previous mode was 0' sentinel in edge cases, the wrapper's check is aggressive, but in practice the trigger is a bad argument or dead DC.
Source
Thrown at v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts:192
* It will be called _synchronously_ with a cancellation cause
* when cancellation is requested, _after_ the promise has already rejected
* with a {@link CancelError}, but _before_
* any {@link then}/{@link catch}/{@link finally} callback runs.
* If the callback returns a thenable, the promise returned from {@link cancel}
* will only fulfill after the former has settled.
* Unhandled exceptions or rejections from the callback will be wrapped
* in a {@link CancelledRejectionError} and bubbled up as unhandled rejections.
* If the `resolve` callback is called before cancellation with a cancellable promise,
* cancellation requests on this promise will be diverted to that promise,
* and the original `oncancelled` callback will be discarded.
*/
constructor(executor: CancellablePromiseExecutor<T>, oncancelled?: CancellablePromiseCanceller) {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: any) => void;
super((res, rej) => { resolve = res; reject = rej; });
if ((this.constructor as any)[species] !== Promise) {
throw new TypeError("CancellablePromise does not support transparent subclassing. Please refrain from overriding the [Symbol.species] static property.");
}
let promise: CancellablePromiseWithResolvers<T> = {
promise: this,
resolve,
reject,
get oncancelled() { return oncancelled ?? null; },
set oncancelled(cb) { oncancelled = cb ?? undefined; }
};
const state: CancellablePromiseState = {
get root() { return state; },
resolving: false,
settled: false
};
// Setup cancellation system.
void Object.defineProperties(this, {View on GitHub (pinned to 0e754b1b40)
Solutions
- Use the package constants: pass w32.TRANSPARENT or w32.OPAQUE (1 and 2) rather than ad-hoc integers.
- Verify the HDC lifetime: SetBkMode must run between BeginPaint/EndPaint or GetDC/ReleaseDC pairs in the same message handler.
- Add a defensive check that the mode is 1 or 2 before calling when the value comes from config or user input.
- If the panic occurs at shutdown, stop the paint pipeline when the window is closing.
Example fix
// before
w32.SetBkMode(hdc, bkMode) // bkMode == 0 from unset config -> panic
// after
if bkMode != w32.TRANSPARENT && bkMode != w32.OPAQUE {
bkMode = w32.OPAQUE
}
w32.SetBkMode(hdc, bkMode) Defensive patterns
Strategy: validation
Validate before calling
func validBkMode(mode int) bool {
return mode == w32.TRANSPARENT || mode == w32.OPAQUE
} Try / catch
defer func() {
if r := recover(); r != nil {
if msg, _ := r.(string); strings.HasPrefix(msg, "SetBkMode failed") {
log.Printf("text background mode rejected, defaulting")
return
}
panic(r)
}
}()
w32.SetBkMode(hdc, mode) Prevention
- Always use the package constants w32.TRANSPARENT / w32.OPAQUE, never raw integers.
- Validate config-supplied mode values against the two legal constants before painting.
- Keep HDC usage inside its owning paint scope.
When it happens
Trigger: Passing a mode constant defined in your own package with the wrong numeric value (e.g. 0 or 3+); calling SetBkMode with an HDC released by EndPaint/ReleaseDC; text-drawing helpers invoked during window destruction.
Common situations: Custom text rendering with TRANSPARENT background set before DrawText; the constant was hand-rolled instead of using the w32 constant so a typo'd value compiles fine but panics at runtime; ordering bugs where painting continues after WM_DESTROY.
Related errors
- CancellablePromise.prototype.then called on an invalid objec
- CancellablePromise.prototype.finally called on an invalid ob
- Invalid JSON passed to callback: ${e.message}. Message: ${in
- Callback '${callbackID}' not registered!!!
- Invalid JSON passed to Notify: ${notifyMessage}
AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15).
Data as JSON: /api/errors/373c942e9392a667.
Report an issue: GitHub.