{"record":{"id":"7d8c381855411937","repo":"wailsapp/wails","slug":"failed-setmenu","errorCode":null,"errorMessage":"failed SetMenu","messagePattern":"failed SetMenu","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/desktop/windows/winc/form.go","lineNumber":108,"sourceCode":"func (fm *Form) SetLayout(mng LayoutManager) {\n\tfm.layoutMng = mng\n}\n\n// UpdateLayout refresh layout.\nfunc (fm *Form) UpdateLayout() {\n\tif fm.layoutMng != nil {\n\t\tfm.layoutMng.Update()\n\t}\n}\n\nfunc (fm *Form) NewMenu() *Menu {\n\thMenu := w32.CreateMenu()\n\tif hMenu == 0 {\n\t\tpanic(\"failed CreateMenu\")\n\t}\n\tm := &Menu{hMenu: hMenu, hwnd: fm.hwnd}\n\tif !w32.SetMenu(fm.hwnd, hMenu) {\n\t\tpanic(\"failed SetMenu\")\n\t}\n\treturn m\n}\n\nfunc (fm *Form) DisableIcon() {\n\twindowInfo := getWindowInfo(fm.hwnd)\n\tframeless := windowInfo.IsPopup()\n\tif frameless {\n\t\treturn\n\t}\n\texStyle := w32.GetWindowLong(fm.hwnd, w32.GWL_EXSTYLE)\n\tw32.SetWindowLong(fm.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME))\n\tw32.SetWindowPos(fm.hwnd, 0, 0, 0, 0, 0,\n\t\tuint(\n\t\t\tw32.SWP_FRAMECHANGED|\n\t\t\t\tw32.SWP_NOMOVE|\n\t\t\t\tw32.SWP_NOSIZE|\n\t\t\t\tw32.SWP_NOZORDER),","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/desktop/windows/winc/form.go#L90-L126","documentation":"After CreateMenu succeeds, Form.NewMenu attaches the menu to the window with SetMenu and panics when it returns FALSE. SetMenu fails when the target HWND is not a valid top-level window of the calling thread, or the window is being destroyed. Unlike the CreateMenu panic, this one is usually about the state of fm.hwnd: menu bars cannot be attached to a child control or an already-destroyed window.","triggerScenarios":"Calling NewMenu on a Form whose HWND was destroyed (call racing app shutdown); calling NewMenu on an object whose InitWindow failed earlier so hwnd is 0; calling from a goroutine on a window owned by another thread (SetMenu requires the window's thread).","commonSituations":"Menu setup dispatched asynchronously while the main window closes; forms created but never fully initialized because an earlier panic/return skipped InitWindow.","solutions":["Call NewMenu on the UI thread during form setup, before the form is shown and long before it can be closed","Guard with w32.IsWindow(fm.Handle()) if menu creation can happen late or asynchronously","If menus must change after close, destroy the form instead and rebuild it rather than mutating a dead HWND"],"exampleFix":"// before\ngo func() { f.NewMenu().Append(...) }() // races window destruction -> SetMenu FALSE -> panic\n\n// after\nfunc (f *MainForm) Setup() { // called on UI thread right after form init\n    if !w32.IsWindow(f.Handle()) { return }\n    m := f.NewMenu()\n    m.Append(...)\n}","handlingStrategy":"validation","validationCode":"func attachMenuSafe(f *winc.Form) *winc.Menu {\n    if !w32.IsWindow(f.Handle()) { return nil } // SetMenu fails on dead HWND\n    return f.NewMenu()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create menus on the UI thread during setup, before the window can close","Do not call NewMenu from goroutines"],"tags":["windows","win32-api","menu","winc","panic","threading"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}