wailsapp/wails · error · TypeError
CancellablePromise.prototype.finally called on an invalid ob
Error message
CancellablePromise.prototype.finally called on an invalid object.
What it means
SetBkColor sets the background color used for text and hatched fills on a DC, returning the previous color. The w32 wrapper panics with 'SetBkColor failed' when the API returns CLR_INVALID, its documented failure code. As with SetTextColor, an invalid/destroyed HDC is the dominant real-world cause; the color argument accepts essentially any COLORREF so it rarely triggers the failure.
Source
Thrown at v3/internal/runtime/desktop/@wailsio/runtime/src/cancellable.ts:487
* This method is implemented in terms of {@link then} and the same caveats apply.
* It is polyfilled, hence available in every OS/webview version.
*
* @returns A Promise for the completion of the callback.
* Cancellation requests on the returned promise
* will propagate up the chain to the parent promise,
* but not in the other direction.
*
* The promise returned from {@link cancel} will fulfill only after all attached handlers
* up the entire promise chain have been run.
*
* If `onfinally` returns a cancellable promise,
* cancellation requests will be diverted to it,
* and the specified `oncancelled` callback will be discarded.
* See {@link then} for more details.
*/
finally(onfinally?: (() => void) | undefined | null, oncancelled?: CancellablePromiseCanceller): CancellablePromise<T> {
if (!(this instanceof CancellablePromise)) {
throw new TypeError("CancellablePromise.prototype.finally called on an invalid object.");
}
if (!isCallable(onfinally)) {
return this.then(onfinally, onfinally, oncancelled);
}
return this.then(
(value) => CancellablePromise.resolve(onfinally()).then(() => value),
(reason?) => CancellablePromise.resolve(onfinally()).then(() => { throw reason; }),
oncancelled,
);
}
/**
* We use the `[Symbol.species]` static property, if available,
* to disable the built-in automatic subclassing features from {@link Promise}.
* It is critical for performance reasons that extenders do not override this.
* Once the proposal at https://github.com/tc39/proposal-rm-builtin-subclassingView on GitHub (pinned to 0e754b1b40)
Solutions
- Scope SetBkColor to the BeginPaint/EndPaint or GetDC/ReleaseDC block that owns the HDC.
- Check for double ReleaseDC/EndPaint — the second release invalidates the handle used later.
- Prefer setting background color right before the text call that needs it, not in long-lived setup, so lifetime is obvious.
- Reproduce with a debug breakpoint on the panic and inspect whether the HDC value matches the one returned by BeginPaint.
Example fix
// before
func onPaint(hdc w32.HDC){ setColorsAsync(hdc) } // async runs after EndPaint
// after
func onPaint(hdc w32.HDC){
w32.SetBkMode(hdc, w32.OPAQUE)
w32.SetBkColor(hdc, bgColor)
drawTextNow(hdc)
} Defensive patterns
Strategy: validation
Try / catch
defer func() {
if r := recover(); r != nil {
if msg, _ := r.(string); strings.HasPrefix(msg, "SetBkColor failed") {
log.Printf("background color skipped: DC likely released")
return
}
panic(r)
}
}()
w32.SetBkColor(hdc, color) Prevention
- Scope all background-color calls to the active paint block.
- Guard against double release of the DC.
- Draw synchronously in the paint handler rather than deferring to callbacks.
When it happens
Trigger: Using an HDC after its owning paint cycle completed (post-EndPaint); calling with a DC whose window was destroyed; inter-leaving w32 GDI calls with a framework that already released the DC it handed you.
Common situations: Opaque text drawing (SetBkMode(OPAQUE) + SetBkColor) performed in a deferred callback that runs after WM_PAINT returns; double-release of a DC in cleanup code.
Related errors
- CancellablePromise does not support transparent subclassing.
- CancellablePromise.prototype.then called on an invalid objec
- 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/4ff471e031566e1d.
Report an issue: GitHub.