I found that manually closing all the docks windows then allows OBS to quit. Claude says this...
After reviewing the source code, I believe the issue is that plugin-created top-level windows (floating docks and source windows) block Qt 6's quit handler on macOS.
When quitting, Qt calls closeAllWindows() then checks for remaining visible top-level windows. If any remain, Qt rejects the quit (e->ignore() in QApplication::event(QEvent::Quit)). On macOS, floating OBSDock widgets and source_windows are independent top-level windows that can survive this check.
Suggested fix
Add Qt::WA_QuitOnClose set to false on all plugin-created top-level windows so they don't block Qt's quit:
window->setAttribute(Qt::WA_QuitOnClose, false);
This should be added in GetSourceWindowByTitle() in source-dock-settings.cpp.
Additional issues found
1. Signal handler copy-paste bug in source-dock.cpp (~line 1635-1638): "activate" is disconnected twice, but "deactivate" is never disconnected before reconnecting. This can lead to multiple OBSActiveChanged callbacks firing on destroyed widgets during shutdown.
signal_handler_disconnect(sh, "activate", OBSActiveChanged, this);
signal_handler_disconnect(sh, "activate", OBSActiveChanged, this); // should be "deactivate"
signal_handler_connect(sh, "activate", OBSActiveChanged, this);
signal_handler_connect(sh, "deactivate", OBSActiveChanged, this);
2. EXIT handler (~line 433-443): source_windows are close()d and deleted synchronously. If closeAllWindows() already closed some of them, this could cause double-free.
Consider using deleteLater() instead.
3. Async tasks (~line 474): update_active is queued to the graphics thread and accesses source_docks, which may have been cleared by the EXIT handler by the time the task executes.