From 1c885e3fc5364d369b9756adea293d05479e3cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Bene=C5=A1?= Date: Sat, 25 Jul 2026 01:36:29 +0200 Subject: [PATCH] Fix low-severity robustness issues - show_error_dialog() aborted when QApplication construction itself was the failure; fall back to stderr so the real error is visible. - Auto-update QThread cleanup dropped the last Python reference while run() could still be unwinding ("QThread: Destroyed while thread is still running"); defer destruction to deleteLater on finished. - macOS ffmpeg install no longer curl|bash-es the Homebrew bootstrap script unattended; it now asks the user to install Homebrew themselves and fails cleanly. Co-Authored-By: Claude Fable 5 --- ytsage/core/ytsage_ffmpeg.py | 11 ++++++++--- ytsage/gui/ytsage_gui_main.py | 14 +++++++++----- ytsage/main.py | 6 ++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/ytsage/core/ytsage_ffmpeg.py b/ytsage/core/ytsage_ffmpeg.py index a604cd2..0bb87f9 100644 --- a/ytsage/core/ytsage_ffmpeg.py +++ b/ytsage/core/ytsage_ffmpeg.py @@ -406,9 +406,14 @@ def install_ffmpeg_macos() -> bool: timeout=5, ) except (subprocess.SubprocessError, FileNotFoundError): - logger.info("Installing Homebrew...") - brew_install_cmd = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' - subprocess.run(brew_install_cmd, shell=True, check=True, timeout=300) + # Never curl|bash a remote script without the user's say-so. + # Homebrew installation is the user's decision to make in a + # terminal, where the script can also prompt for sudo properly. + logger.error( + "Homebrew is not installed. Install it from https://brew.sh and retry, " + "or install ffmpeg another way." + ) + return False # Install FFmpeg logger.info("Installing FFmpeg...") diff --git a/ytsage/gui/ytsage_gui_main.py b/ytsage/gui/ytsage_gui_main.py index 4958f50..1da5f0b 100644 --- a/ytsage/gui/ytsage_gui_main.py +++ b/ytsage/gui/ytsage_gui_main.py @@ -1176,12 +1176,16 @@ class YTSageApp(QMainWindow, FormatTableMixin, VideoInfoMixin, AnalysisMixin): # Clean up the thread reference and ensure it's properly finished if hasattr(self, "auto_update_thread"): + thread = self.auto_update_thread # Disconnect all signals to prevent further callbacks - self.auto_update_thread.update_finished.disconnect() - # Make sure thread is finished - if self.auto_update_thread.isRunning(): - self.auto_update_thread.quit() - self.auto_update_thread.wait(1000) # Wait up to 1 second + thread.update_finished.disconnect() + # Dropping the Python reference while run() is still unwinding + # can destroy a live QThread; let Qt delete it once finished. + if thread.isRunning(): + thread.finished.connect(thread.deleteLater) + thread.quit() + else: + thread.deleteLater() # Remove the reference delattr(self, "auto_update_thread") diff --git a/ytsage/main.py b/ytsage/main.py index 6427578..f77e2db 100644 --- a/ytsage/main.py +++ b/ytsage/main.py @@ -7,6 +7,12 @@ from .gui.ytsage_gui_main import YTSageApp # Import the main application class def show_error_dialog(message): + # A QMessageBox needs a live QApplication; if startup failed before (or + # while) creating one, constructing the dialog would abort the process + # and swallow the real error. + if QApplication.instance() is None: + print(f"Application Error: {message}", file=sys.stderr) + return error_dialog = QMessageBox() error_dialog.setIcon(QMessageBox.Icon.Critical) error_dialog.setText("Application Error")