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-subclassing

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Scope SetBkColor to the BeginPaint/EndPaint or GetDC/ReleaseDC block that owns the HDC.
  2. Check for double ReleaseDC/EndPaint — the second release invalidates the handle used later.
  3. Prefer setting background color right before the text call that needs it, not in long-lived setup, so lifetime is obvious.
  4. 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

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


AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15). Data as JSON: /api/errors/4ff471e031566e1d. Report an issue: GitHub.