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

  1. Read the appended Obj-C exception text - it names the NSException reason; fix the menu construction it points at
  2. Simplify the MainMenu: no ChildMenu on separators, no duplicated special labels (About/Settings/Preferences), shallow nesting
  3. 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

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


AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15). Data as JSON: /api/errors/7c4983c1183741e8. Report an issue: GitHub.