wailsapp/wails · critical

syscall.GetLastError()

Error message

syscall.GetLastError()

What it means

RegisterWindow panicked because RegisterClassEx returned atom 0. The most common cause is ERROR_CLASS_ALREADY_EXISTS (1410): the window class name is already registered in the process — by a previous, not-yet-unregistered registration (the guard map in window.go only tracks classes registered through this helper). The panic value is syscall.GetLastError(), which is also unreliable in Go because the runtime may issue syscalls between the failing call and the check.

Source

Thrown at v3/pkg/w32/window.go:298

		return classInstance, nil
	}
	applicationInstance := GetModuleHandle("")
	if applicationInstance == 0 {
		return 0, fmt.Errorf("get module handle failed")
	}

	var wc WNDCLASSEX
	wc.Size = uint32(unsafe.Sizeof(wc))
	wc.WndProc = syscall.NewCallback(proc)
	wc.Instance = applicationInstance
	wc.Icon = LoadIconWithResourceID(0, uint16(IDI_APPLICATION))
	wc.Cursor = LoadCursorWithResourceID(0, uint16(IDC_ARROW))
	wc.Background = COLOR_BTNFACE + 1
	wc.ClassName = MustStringToUTF16Ptr(name)

	atom := RegisterClassEx(&wc)
	if atom == 0 {
		panic(syscall.GetLastError())
	}

	setWindowClass(name, applicationInstance)

	return applicationInstance, nil
}

func FlashWindow(hwnd HWND, enabled bool) {
	var flashInfo FLASHWINFO
	flashInfo.CbSize = uint32(unsafe.Sizeof(flashInfo))
	flashInfo.Hwnd = hwnd
	if enabled {
		flashInfo.DwFlags = FLASHW_ALL | FLASHW_TIMERNOFG
	} else {
		flashInfo.DwFlags = FLASHW_STOP
	}
	_, _, _ = procFlashWindowEx.Call(uintptr(unsafe.Pointer(&flashInfo)))
}

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Route every registration through the same name (the helper already dedupes via getWindowClass), or check GetLastError for ERROR_CLASS_ALREADY_EXISTS and treat it as success
  2. Call UnregisterClass(name, instance) during shutdown before re-registering in the same process
  3. Use a unique class name (append PID or a random suffix) when collisions are possible
  4. Capture the error with the windows errno immediately (e.g. use w32 helper or syscall.Errno captured right after the call), not a deferred GetLastError

Example fix

// before
atom := RegisterClassEx(&wc)
if atom == 0 {
    panic(syscall.GetLastError())
}

// after
atom := RegisterClassEx(&wc)
if atom == 0 {
    if errno.ERROR_CLASS_ALREADY_EXISTS == w32.GetLastErrorImmediate() {
        setWindowClass(name, applicationInstance)
        return applicationInstance, nil
    }
    return 0, fmt.Errorf("RegisterClassEx failed: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// dedupe before registering
if _, exists := getWindowClass(name); exists {
    return existing, nil
}
// treat already-registered as success
atom := w32.RegisterClassEx(&wc)
if atom == 0 && w32.GetLastError() == 1410 { // ERROR_CLASS_ALREADY_EXISTS
    setWindowClass(name, hInst)
    return hInst, nil
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("RegisterClassEx(%q) failed: %v", name, r)
    }
}()

Prevention

When it happens

Trigger: Calling w32.RegisterWindow twice with the same class name from different code paths that bypass the windowClasses map (or after the map was cleared in tests); registering a class after the process previously used the same name without UnregisterClass; the ClassName UTF-16 buffer being invalid.

Common situations: Running the Wails app and an embedded second window system in the same process; test binaries that start/stop the application multiple times; class-name collisions when two libraries pick the same default name.

Related errors


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