fyne-io/fyne · critical
unhandled Obj-C exception:
Error message
unhandled Obj-C exception:
What it means
On macOS, native menu construction routes Obj-C NSExceptions into the exported exceptionCallback. If an exception arrives and no Go handler is registered in ecb (setExceptionCallback - currently only fyne's own tests register one), the callback re-panics with the Obj-C message so the crash is at least descriptive. In a normal app ecb is nil, so any Obj-C exception during menu setup surfaces as this panic.
Source
Thrown at internal/driver/glfw/menu_darwin.go:144
}
func createNativeMenu(w *window, menu *fyne.Menu, nextItemID int) (unsafe.Pointer, int) {
nsMenu := C.createDarwinMenu(C.CString(menu.Label))
for _, item := range menu.Items {
nsMenuItem := insertNativeMenuItem(nsMenu, item, nextItemID, -1)
nextItemID = registerCallback(w, item, nextItemID)
if item.ChildMenu != nil {
nextItemID = addNativeSubmenu(w, nsMenuItem, item.ChildMenu, nextItemID)
}
}
return nsMenu, nextItemID
}
//export exceptionCallback
func exceptionCallback(e *C.char) {
msg := C.GoString(e)
if ecb == nil {
panic("unhandled Obj-C exception: " + msg)
}
ecb(msg)
}
func handleSpecialItems(w *window, menu *fyne.Menu, nextItemID int, addSeparator bool) (*fyne.Menu, int) {
menu = fyne.NewMenu(menu.Label, menu.Items...) // copy so we can manipulate
for i := 0; i < len(menu.Items); i++ {
item := menu.Items[i]
switch item.Label {
case "About", "Settings", "Settings…", "Preferences", "Preferences…":
items := make([]*fyne.MenuItem, 0, len(menu.Items)-1)
items = append(items, menu.Items[:i]...)
items = append(items, menu.Items[i+1:]...)
menu, nextItemID = handleSpecialItems(w, fyne.NewMenu(menu.Label, items...), nextItemID, false)
i--
insertNativeMenuItem(C.darwinAppMenu(), item, nextItemID, 1)
if addSeparator && item.Label != "About" {View on GitHub (pinned to 8860ee95c3)
Solutions
- Read the appended Obj-C exception text - it names the NSException reason; fix the menu construction it points at
- Simplify the MainMenu: no ChildMenu on separators, no duplicated special labels (About/Settings/Preferences), shallow nesting
- Update fyne (native menu code is patched often); on the latest release, report the exception text plus stack upstream
Example fix
// before - submenu on a separator item raises NSException in AppKit
sep := fyne.NewMenuItemSeparator()
sep.ChildMenu = fyne.NewMenu("Tools")
menu := fyne.NewMenu("Main", sep)
// after - child menus hang off normal items
item := fyne.NewMenuItem("Tools", nil)
item.ChildMenu = fyne.NewMenu("Tools")
menu := fyne.NewMenu("Main", item) Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: lint menu structure for shapes known to upset AppKit.
func menuValid(m *fyne.Menu) error {
for _, it := range m.Items {
if it.IsSeparator && it.ChildMenu != nil {
return fmt.Errorf("submenu attached to separator item %q", it.Label)
}
}
return nil
}
if err := menuValid(mainMenu.Items[i]); err != nil { return err } Prevention
- Keep native menus shallow and standard; avoid exotic item structures
- Never attach ChildMenu to separators; put special labels (About/Preferences) at top level once
- Test menu setup on the oldest macOS version you support after every AppKit-adjacent update
When it happens
Trigger: An NSException thrown while creating/inserting native menu items on macOS (setupNativeMenu / addNativeMenu path) - for example structurally invalid menu trees (submenus on separators, inconsistent indexes) or AppKit state races during menu rebuild.
Common situations: Crashes shortly after startup when assigning a MainMenu with unusual structure; menu rebuilds while the app menu is being mutated; macOS/AppKit behavior changes after OS updates; deeply nested or duplicated special items (About/Preferences).
Related errors
- unimplemented; GOOS/CGO combination not supported
- unhandled token type: %T %+v
- encountered empty values slice
- TODO only know how to handle DataIntDec type here
- string lengths over 1<<15 not yet supported, got len %d
AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15).
Data as JSON: /api/errors/7c4983c1183741e8.
Report an issue: GitHub.