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
- 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
- Call UnregisterClass(name, instance) during shutdown before re-registering in the same process
- Use a unique class name (append PID or a random suffix) when collisions are possible
- 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
- Route all class registrations through one deduplicating helper
- Unregister classes on shutdown if the process restarts windowing
- Use unique class names (suffix PID) when colliding with other libraries
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
- invalid stream batch acknowledgement
- no element matches selector: ' + target.selector
- failed CreateMenu
- DrawMenuBar failed
- RadioGroup.MenuID: item not found:
AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15).
Data as JSON: /api/errors/ef9dda8a838c806b.
Report an issue: GitHub.