wailsapp/wails · error

initMainLoop was not called

Error message

initMainLoop was not called

What it means

On Windows, windowsApp.invokeRequired() compares the current thread ID against the main thread ID captured during main-loop initialization; if mainThreadID is still 0 it panics with 'initMainLoop was not called'. It means UI-thread affinity logic ran before the application main loop was initialized.

Source

Thrown at v3/pkg/application/mainthread_windows.go:96

	if m.invokeRequired() {
		w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0)
	} else {
		mainThreadFunctionStoreLock.Lock()
		fn := mainThreadFunctionStore[id]
		delete(mainThreadFunctionStore, id)
		mainThreadFunctionStoreLock.Unlock()

		if fn == nil {
			Fatal("dispatchOnMainThread called with invalid id: %v", id)
		}
		fn()
	}
}

func (m *windowsApp) invokeRequired() bool {
	mainThreadID := m.mainThreadID
	if mainThreadID == 0 {
		panic("initMainLoop was not called")
	}

	return mainThreadID != w32.GetCurrentThreadId()
}

func (m *windowsApp) invokeCallback(wParam, lParam uintptr) {
	// TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings...
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()
	if m.invokeRequired() {
		panic("invokeCallback must always be called on the MainOSThread")
	}

	mainThreadFunctionStoreLock.Lock()
	fnIDs := make([]uint, 0, len(mainThreadFunctionStore))
	for id := range mainThreadFunctionStore {
		fnIDs = append(fnIDs, id)
	}

View on GitHub (pinned to 0e754b1b40)

Solutions

  1. Defer any UI or dispatch calls until after the application main loop is running (inside service Startup hooks or later)
  2. In tests, initialize the main loop / use the app test harness instead of calling windows UI helpers directly
  3. Review startup ordering so nothing touches invokeRequired before initMainLoop executes

Example fix

// before
func NewApp() *App {
    a := &App{}
    showNotification() // dispatches before main loop: panics
    return a
}

// after
func (a *App) Startup(ctx context.Context) {
    showNotification() // main loop running: safe
}
Defensive patterns

Strategy: validation

Validate before calling

// only dispatch UI work once the app is running
if appStarted.Load() {
    app.DispatchOnMainThread(fn)
} else {
    queueUntilStartup(fn) // drain inside your Startup hook
}

Prevention

When it happens

Trigger: Any code path that reaches invokeRequired() (e.g. invokeCallback or dispatch machinery) before the app's main loop initialized the main thread ID — typically invoking/dispatching work before app.Run() has started the main loop, or using windowing APIs in unit tests that never start the app.

Common situations: Calling dialog/notification/window helpers from an init() or from a service constructor that runs before the app starts; tests exercising Windows app code without booting the main loop; ordering changes in startup code after a v3 upgrade.

Related errors


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