diff --git a/beatport_unified_scraper.py b/beatport_unified_scraper.py index c671781e..950edded 100644 --- a/beatport_unified_scraper.py +++ b/beatport_unified_scraper.py @@ -2612,7 +2612,7 @@ class BeatportUnifiedScraper: result['title'] = title except Exception as e: - pass # Silently handle URL extraction errors + logger.debug("URL slug extraction: %s", e) return result diff --git a/core/automation_engine.py b/core/automation_engine.py index e7a2b6e7..d19cbb58 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -455,8 +455,10 @@ class AutomationEngine: if delay_minutes and delay_minutes > 0: # Initialize progress BEFORE delay so card glows during wait if self._progress_init_fn: - try: self._progress_init_fn(automation_id, auto.get('name', ''), action_type) - except Exception: pass + try: + self._progress_init_fn(automation_id, auto.get('name', ''), action_type) + except Exception as e: + logger.debug("event progress init (delay): %s", e) _delay_already_inited = True delay_seconds = int(delay_minutes) * 60 @@ -486,13 +488,17 @@ class AutomationEngine: logger.info(f"Event automation '{auto.get('name')}' skipped — {action_type} busy") # If progress was initialized during delay, finalize it if _delay_already_inited and self._progress_finish_fn: - try: self._progress_finish_fn(automation_id, result) - except Exception: pass + try: + self._progress_finish_fn(automation_id, result) + except Exception as e: + logger.debug("event progress finish (skipped): %s", e) else: # Initialize progress tracking (skip if already done during delay) if not _delay_already_inited and self._progress_init_fn: - try: self._progress_init_fn(automation_id, auto.get('name', ''), action_type) - except Exception: pass + try: + self._progress_init_fn(automation_id, auto.get('name', ''), action_type) + except Exception as e: + logger.debug("event progress init: %s", e) try: result = handler_info['handler'](action_config) or {} logger.info(f"Event automation '{auto.get('name')}' executed: {result.get('status', 'ok')}") @@ -501,8 +507,10 @@ class AutomationEngine: logger.error(f"Event automation '{auto.get('name')}' action failed: {e}") # Finalize progress tracking if self._progress_finish_fn: - try: self._progress_finish_fn(automation_id, result) - except Exception: pass + try: + self._progress_finish_fn(automation_id, result) + except Exception as e: + logger.debug("event progress finish: %s", e) # Merge event data into result for then-action variables merged = {**event_data, **result} @@ -580,8 +588,10 @@ class AutomationEngine: if not skip_delay and delay_minutes and delay_minutes > 0: # Initialize progress BEFORE delay so card glows during wait if self._progress_init_fn: - try: self._progress_init_fn(automation_id, auto.get('name', ''), action_type) - except Exception: pass + try: + self._progress_init_fn(automation_id, auto.get('name', ''), action_type) + except Exception as e: + logger.debug("scheduled progress init (delay): %s", e) _delay_already_inited = True delay_seconds = int(delay_minutes) * 60 @@ -603,15 +613,19 @@ class AutomationEngine: logger.info(f"Automation '{auto['name']}' skipped — {action_type} already running") # If progress was initialized during delay, finalize it if _delay_already_inited and self._progress_finish_fn: - try: self._progress_finish_fn(automation_id, result) - except Exception: pass + try: + self._progress_finish_fn(automation_id, result) + except Exception as e: + logger.debug("scheduled progress finish (skipped): %s", e) self._finish_run(auto, automation_id, result, error=None) return # Initialize progress tracking (skip if already done during delay) if not _delay_already_inited and self._progress_init_fn: - try: self._progress_init_fn(automation_id, auto.get('name', ''), action_type) - except Exception: pass + try: + self._progress_init_fn(automation_id, auto.get('name', ''), action_type) + except Exception as e: + logger.debug("scheduled progress init: %s", e) # Execute the action error = None @@ -637,8 +651,10 @@ class AutomationEngine: # Finalize progress tracking if self._progress_finish_fn: - try: self._progress_finish_fn(automation_id, result) - except Exception: pass + try: + self._progress_finish_fn(automation_id, result) + except Exception as e: + logger.debug("scheduled progress finish: %s", e) # Execute then-actions (notifications + fire_signal) try: diff --git a/core/connection_detect.py b/core/connection_detect.py index fd23361f..ab19e3a9 100644 --- a/core/connection_detect.py +++ b/core/connection_detect.py @@ -79,9 +79,9 @@ def run_detection(server_type): api_response = requests.get(api_url, timeout=1) if api_response.status_code == 200 and 'MediaContainer' in api_response.text: return f"http://{ip}:{port}" - - except: - pass + + except Exception as e: + logger.debug("plex probe %s: %s", ip, e) return None def test_jellyfin_server(ip, port=8096): @@ -101,9 +101,9 @@ def run_detection(server_type): web_response = requests.get(web_url, timeout=1) if web_response.status_code == 200 and 'jellyfin' in web_response.text.lower(): return f"http://{ip}:{port}" - - except: - pass + + except Exception as e: + logger.debug("jellyfin probe %s: %s", ip, e) return None def test_slskd_server(ip, port=5030): @@ -117,8 +117,8 @@ def run_detection(server_type): if response.status_code in [200, 401]: return f"http://{ip}:{port}" - except: - pass + except Exception as e: + logger.debug("slskd probe %s: %s", ip, e) return None def test_navidrome_server(ip, port=4533): @@ -140,8 +140,8 @@ def run_detection(server_type): # Check for Subsonic/Navidrome API response structure if 'subsonic-response' in data: return f"http://{ip}:{port}" - except: - pass + except Exception as e: + logger.debug("navidrome json parse: %s", e) # Also try the web interface web_url = f"http://{ip}:{port}/" @@ -149,8 +149,8 @@ def run_detection(server_type): if web_response.status_code == 200 and 'navidrome' in web_response.text.lower(): return f"http://{ip}:{port}" - except: - pass + except Exception as e: + logger.debug("navidrome probe %s: %s", ip, e) return None try: diff --git a/core/database_update_worker.py b/core/database_update_worker.py index 943cd444..3262ee30 100644 --- a/core/database_update_worker.py +++ b/core/database_update_worker.py @@ -1051,8 +1051,8 @@ class DatabaseUpdateWorker: batch + [self.server_type]) cascade_album_ids.update(row[0] for row in cursor.fetchall()) removed_album_ids -= cascade_album_ids - except Exception: - pass # If this optimization fails, double-delete is harmless + except Exception as e: + logger.debug("cascade album cleanup optimization: %s", e) if not removed_artist_ids and not removed_album_ids: logger.info("Removal detection: no stale content found") diff --git a/core/jellyfin_client.py b/core/jellyfin_client.py index 2439e1cf..1257d4ab 100644 --- a/core/jellyfin_client.py +++ b/core/jellyfin_client.py @@ -1441,8 +1441,8 @@ class JellyfinClient(MediaServerClient): response = requests.delete(url, headers=headers, timeout=10) if response.status_code in [200, 204]: logger.info(f"Deleted existing backup playlist '{target_name}'") - except Exception: - pass # Target doesn't exist, which is fine + except Exception as e: + logger.debug("backup playlist precheck: %s", e) # Create new playlist with copied tracks try: diff --git a/core/library/track_identity.py b/core/library/track_identity.py index 3d2ff982..2b43650d 100644 --- a/core/library/track_identity.py +++ b/core/library/track_identity.py @@ -208,7 +208,7 @@ def find_library_track_by_external_id( if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass @@ -287,7 +287,7 @@ def find_provenance_by_external_id( if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass diff --git a/core/library_reorganize.py b/core/library_reorganize.py index b499c4b0..a0fb86f4 100644 --- a/core/library_reorganize.py +++ b/core/library_reorganize.py @@ -506,7 +506,7 @@ def load_album_and_tracks(db, album_id): if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass @@ -1329,7 +1329,7 @@ def reorganize_album( try: if os.path.isdir(staging_album_dir): shutil.rmtree(staging_album_dir, ignore_errors=True) - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass # Best-effort cleanup of source directories. For each touched dir @@ -1343,14 +1343,14 @@ def reorganize_album( if _has_remaining_audio(src_dir): continue _delete_album_sidecars(src_dir) - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass if cleanup_empty_dir_fn: for src_dir in src_dirs_touched: try: cleanup_empty_dir_fn(src_dir) - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass # Prune empty *destination* siblings — e.g. when a previous diff --git a/core/listenbrainz_manager.py b/core/listenbrainz_manager.py index ad964646..914a85ce 100644 --- a/core/listenbrainz_manager.py +++ b/core/listenbrainz_manager.py @@ -302,8 +302,8 @@ class ListenBrainzManager: # Fallback to first image if images: return images[0].get('thumbnails', {}).get('small') or images[0].get('image') - except: - pass + except Exception as e: + logger.debug("cover-art fetch: %s", e) return None diff --git a/core/listening_stats_worker.py b/core/listening_stats_worker.py index ec70e1fd..e2445aac 100644 --- a/core/listening_stats_worker.py +++ b/core/listening_stats_worker.py @@ -507,7 +507,7 @@ class ListeningStatsWorker: if conn: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass def _resolve_db_track_id(self, title, artist): diff --git a/core/metadata/album_mbid_cache.py b/core/metadata/album_mbid_cache.py index 2d92247e..ba32889f 100644 --- a/core/metadata/album_mbid_cache.py +++ b/core/metadata/album_mbid_cache.py @@ -102,7 +102,7 @@ def lookup(normalized_album_key: str, artist_key: str) -> Optional[str]: if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass return None @@ -142,7 +142,7 @@ def record(normalized_album_key: str, artist_key: str, release_mbid: str) -> boo if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass @@ -168,7 +168,7 @@ def clear_all() -> bool: if conn is not None: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass diff --git a/core/navidrome_client.py b/core/navidrome_client.py index 5dd29136..11995dff 100644 --- a/core/navidrome_client.py +++ b/core/navidrome_client.py @@ -922,8 +922,8 @@ class NavidromeClient(MediaServerClient): if target_playlist: self._make_request('deletePlaylist', {'id': target_playlist.id}) logger.info(f"Deleted existing backup playlist '{target_name}'") - except Exception: - pass # Target doesn't exist, which is fine + except Exception as e: + logger.debug("backup playlist precheck: %s", e) # Create new playlist with copied tracks try: diff --git a/core/playlists/explorer.py b/core/playlists/explorer.py index 2a5cea8c..7a4603d8 100644 --- a/core/playlists/explorer.py +++ b/core/playlists/explorer.py @@ -224,8 +224,8 @@ def playlist_explorer_build_tree(deps: PlaylistExplorerDeps): cursor.execute("SELECT title FROM albums WHERE artist_id = ?", (ar['id'],)) for alb_row in cursor.fetchall(): owned_titles.add((alb_row['title'] or '').strip().lower()) - except Exception: - pass # Non-critical — owned badges just won't show + except Exception as e: + logger.debug("owned-titles lookup: %s", e) # Build release list releases = [] diff --git a/core/reorganize_runner.py b/core/reorganize_runner.py index 82ffe2ab..8ef7817b 100644 --- a/core/reorganize_runner.py +++ b/core/reorganize_runner.py @@ -101,9 +101,9 @@ def build_runner( def _on_progress(updates): try: get_queue().update_active_progress(queue_id=item.queue_id, **updates) - except Exception: + except Exception as e: # Progress fan-out failures must never break a run. - pass + logger.debug("reorganize progress fan-out: %s", e) return reorganize_album( album_id=item.album_id, diff --git a/core/repair_jobs/library_reorganize.py b/core/repair_jobs/library_reorganize.py index 0f9af632..6d7e68d2 100644 --- a/core/repair_jobs/library_reorganize.py +++ b/core/repair_jobs/library_reorganize.py @@ -399,7 +399,7 @@ class LibraryReorganizeJob(RepairJob): if conn: try: conn.close() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass def _get_setting(self, context: JobContext, key: str, default): diff --git a/core/seasonal_discovery.py b/core/seasonal_discovery.py index eea51c7a..159776f7 100644 --- a/core/seasonal_discovery.py +++ b/core/seasonal_discovery.py @@ -167,8 +167,8 @@ class SeasonalDiscoveryService: try: cursor.execute(f"ALTER TABLE {table} ADD COLUMN source TEXT NOT NULL DEFAULT 'spotify'") conn.commit() - except Exception: - pass # Column already exists + except Exception as e: + logger.debug("source column migration %s: %s", table, e) logger.info("Seasonal discovery database schema initialized") diff --git a/core/soulseek_client.py b/core/soulseek_client.py index 2f7e3261..1f7cb9c7 100644 --- a/core/soulseek_client.py +++ b/core/soulseek_client.py @@ -216,8 +216,8 @@ class SoulseekClient(DownloadSourcePlugin): if session: try: await session.close() - except: - pass + except Exception as _e: + logger.debug("aiohttp session close: %s", _e) async def _make_direct_request(self, method: str, endpoint: str, **kwargs) -> Optional[Dict[str, Any]]: """Make a direct request to slskd without /api/v0/ prefix (for endpoints that work directly)""" @@ -273,9 +273,9 @@ class SoulseekClient(DownloadSourcePlugin): if session: try: await session.close() - except: - pass - + except Exception as _e: + logger.debug("aiohttp direct session close: %s", _e) + def _process_search_responses(self, responses_data: List[Dict[str, Any]]) -> tuple[List[TrackResult], List[AlbumResult]]: """Process search response data into TrackResult and AlbumResult objects""" from collections import defaultdict @@ -1621,8 +1621,8 @@ class SoulseekClient(DownloadSourcePlugin): if resp.status in [200, 405]: # 405 means endpoint exists but wrong method available_endpoints[f"direct_{endpoint}"] = f"Status: {resp.status}" logger.info(f"[OK] Direct endpoint available: {simple_url} (Status: {resp.status})") - except: - pass + except Exception as _e: + logger.debug("direct endpoint probe %s: %s", endpoint, _e) finally: await session.close() diff --git a/core/spotify_client.py b/core/spotify_client.py index 7a142dc4..c0d8ca4f 100644 --- a/core/spotify_client.py +++ b/core/spotify_client.py @@ -1636,8 +1636,8 @@ class SpotifyClient: try: albums_list = cached.get('_albums', cached) if isinstance(cached, dict) else cached return [Album.from_spotify_album(ad) for ad in albums_list] - except Exception: - pass # Cache data incompatible, re-fetch + except Exception as e: + logger.debug("artist albums cache reuse: %s", e) if self.is_spotify_authenticated(): try: diff --git a/core/watchlist/auto_scan.py b/core/watchlist/auto_scan.py index bba8daca..759d43f7 100644 --- a/core/watchlist/auto_scan.py +++ b/core/watchlist/auto_scan.py @@ -460,7 +460,7 @@ def process_watchlist_scan_automatically(automation_id=None, profile_id=None, de # Clear one-time rescan cutoff after full scan cycle try: scanner._clear_rescan_cutoff() - except Exception: + except Exception: # noqa: S110 — finally-block cleanup, logger may be torn down pass # Always reset flag diff --git a/core/watchlist_scanner.py b/core/watchlist_scanner.py index 1ebd73f2..c90e95bd 100644 --- a/core/watchlist_scanner.py +++ b/core/watchlist_scanner.py @@ -3107,8 +3107,8 @@ class WatchlistScanner: release_date = datetime.strptime(release_date_str, "%Y-%m-%d") days_old = (datetime.now() - release_date).days is_new = days_old <= 30 - except: - pass + except Exception as e: + logger.debug("new-release date parse: %s", e) # Add each track to discovery pool for track in tracks: @@ -3552,8 +3552,8 @@ class WatchlistScanner: if release_date_str and len(release_date_str) >= 10: release_date = datetime.strptime(release_date_str[:10], "%Y-%m-%d") days_old = (datetime.now() - release_date).days - except: - pass + except Exception as e: + logger.debug("release-date parse: %s", e) for track in album_data['tracks'].get('items', []): track_id = track.get('id') diff --git a/core/workers/metadata_update.py b/core/workers/metadata_update.py index abc146fa..7b237f0b 100644 --- a/core/workers/metadata_update.py +++ b/core/workers/metadata_update.py @@ -419,10 +419,10 @@ class WebMetadataUpdateWorker: try: for album in artist.albums(): if hasattr(album, 'genres') and album.genres: - album_genres.update(genre.tag if hasattr(genre, 'tag') else str(genre) + album_genres.update(genre.tag if hasattr(genre, 'tag') else str(genre) for genre in album.genres) - except Exception: - pass # Albums might not be accessible + except Exception as e: + logger.debug("artist album genre walk: %s", e) # Combine all genres (prioritize Spotify genres) all_genres = spotify_genres.union(album_genres) diff --git a/core/youtube_client.py b/core/youtube_client.py index b40f76cb..4f1c4b76 100644 --- a/core/youtube_client.py +++ b/core/youtube_client.py @@ -1190,8 +1190,8 @@ class YouTubeClient(DownloadSourcePlugin): album_data = track_details.get('album', {}) if album_data.get('artists'): album_artist = album_data['artists'][0] - except: - pass + except Exception as e: + logger.debug("spotify album artist lookup: %s", e) logger.debug(" Setting metadata tags...") diff --git a/database/music_database.py b/database/music_database.py index 47d2a5dc..857d16b0 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -5219,8 +5219,8 @@ class MusicDatabase: file_path=file_path, thumb_url=album_row[1] if album_row and len(album_row) > 1 else None ) - except Exception: - pass # Non-critical history logging + except Exception as e: + logger.debug("history logging: %s", e) return True @@ -12137,5 +12137,5 @@ def close_database(): db_instance.close() except Exception as e: # Ignore threading errors during shutdown - pass + logger.debug("db instance close: %s", e) _database_instances.clear() diff --git a/pyproject.toml b/pyproject.toml index 3ef6e9d2..f82a69fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ line-length = 160 [tool.ruff.lint] # Start lenient — catch real bugs, not style nits # E: pycodestyle errors, F: pyflakes, UP: pyupgrade, B: bugbear -select = ["F", "E9", "W6", "B"] +select = ["F", "E9", "W6", "B", "S110"] # Ignore rules that will flag too much in an existing codebase ignore = [ "F401", # unused imports (too noisy initially) @@ -15,7 +15,7 @@ ignore = [ [tool.ruff.lint.per-file-ignores] # Tests can use assert, magic values, etc. -"tests/**" = ["B", "F"] +"tests/**" = ["B", "F", "S110"] [tool.pytest.ini_options] markers = [ diff --git a/web_server.py b/web_server.py index f2fe8d57..9599fef7 100644 --- a/web_server.py +++ b/web_server.py @@ -387,8 +387,8 @@ def _set_profile_context(): session.pop('profile_id', None) from flask import jsonify as _jsonify return _jsonify({"error": "profile_required", "message": "Profile no longer exists"}), 401 - except Exception: - pass # DB error — don't block requests, use the session value + except Exception as e: + logger.debug("profile session validate: %s", e) g.profile_id = pid @@ -515,8 +515,8 @@ def check_download_permission(): profile = get_database().get_profile(pid) if profile and not profile.get('can_download', True): return jsonify({'success': False, 'error': 'Downloads are disabled for this profile.'}), 403 - except Exception: - pass # DB error — don't block + except Exception as e: + logger.debug("download permission check: %s", e) return None # --- Docker Helper Functions --- @@ -1397,8 +1397,8 @@ def _register_automation_handlers(): 'status': 'skipped', 'reason': f'All {len(tracks_json)} tracks unchanged since last sync', } - except Exception: - pass # If we can't read last status, just run the sync + except Exception as e: + logger.debug("mirror sync last-status read: %s", e) _update_automation_progress(auto_id, progress=50, phase=f'Syncing "{pl["name"]}"', @@ -2974,13 +2974,13 @@ def _atexit_save_history(): try: from core.api_call_tracker import api_call_tracker api_call_tracker.save() - except Exception: + except Exception: # noqa: S110 — atexit handler, log handles may be closed pass def _atexit_shutdown(): try: _shutdown_runtime_components() - except Exception: + except Exception: # noqa: S110 — atexit handler, log handles may be closed pass @@ -4176,8 +4176,8 @@ def handle_settings(): # FIX: Re-instantiate the global tidal_client to pick up new settings try: tidal_client = TidalClient() - except Exception: - pass # Keep existing tidal_client if re-init fails + except Exception as e: + logger.debug("tidal client re-init: %s", e) # Reload enrichment worker clients for key-based services if lastfm_worker: lastfm_worker._init_client() @@ -4330,8 +4330,8 @@ def hydrabase_connect(): if _hydrabase_ws: try: _hydrabase_ws.close() - except: - pass + except Exception as _e: + logger.debug("hydrabase connect-existing close: %s", _e) ws = websocket.create_connection( url, header={"x-api-key": api_key}, @@ -4356,8 +4356,8 @@ def hydrabase_disconnect(): if _hydrabase_ws: try: _hydrabase_ws.close() - except: - pass + except Exception as e: + logger.debug("hydrabase disconnect close: %s", e) _hydrabase_ws = None config_manager.set('hydrabase.auto_connect', False) # Only disable dev mode if not using Hydrabase as a regular fallback source @@ -4428,8 +4428,8 @@ def hydrabase_send(): with _hydrabase_lock: try: _hydrabase_ws.close() - except: - pass + except Exception as _e: + logger.debug("hydrabase send close: %s", _e) _hydrabase_ws = None return jsonify({"success": False, "error": str(e)}), 500 @@ -15761,8 +15761,8 @@ def backup_database_endpoint(): try: with open(meta_path, 'w') as mf: json.dump({"version": SOULSYNC_VERSION, "created": timestamp}, mf) - except Exception: - pass # Non-critical — backup still works without metadata + except Exception as e: + logger.debug("backup meta sidecar write: %s", e) # Rolling cleanup existing = sorted(_glob.glob(f"{db_path}.backup_*"), key=os.path.getmtime) # Filter out .meta.json files from the backup list @@ -27002,8 +27002,8 @@ def get_seasonal_playlist(season_key): try: import json track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) - except: - pass + except Exception as e: + logger.debug("track_data_json parse: %s", e) tracks.append(track_dict) else: # Try discovery_pool as fallback (filtered by source) @@ -27029,8 +27029,8 @@ def get_seasonal_playlist(season_key): try: import json track_dict['track_data_json'] = json.loads(track_dict['track_data_json']) - except: - pass + except Exception as e: + logger.debug("discovery track_data_json parse: %s", e) tracks.append(track_dict) config = SEASONAL_CONFIG[season_key] @@ -32212,8 +32212,8 @@ def start_oauth_callback_servers(): self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(f'
{str(e)}
'.encode()) - except Exception: - pass # Connection already broken, nothing more we can do + except Exception as _e: + _oauth_logger.debug("oauth response write: %s", _e) def log_message(self, format, *args): pass # Suppress BaseHTTPRequestHandler access logs (we use our own logger) @@ -34193,8 +34193,8 @@ def _hydrabase_reconnect_loop(): if _hydrabase_ws is not None and _hydrabase_ws.connected: _consecutive_failures = 0 continue - except Exception: - pass # Socket in bad state — treat as disconnected + except Exception as e: + logger.debug("hydrabase socket check: %s", e) # Disconnected with auto_connect enabled — try to reconnect # Back off: 30s, 60s, 120s, max 300s between attempts @@ -34208,8 +34208,8 @@ def _hydrabase_reconnect_loop(): if _hydrabase_ws: try: _hydrabase_ws.close() - except: - pass + except Exception as e: + logger.debug("hydrabase reconnect close: %s", e) ws = websocket.create_connection( hydra_cfg['url'], header={"x-api-key": hydra_cfg['api_key']}, @@ -34224,8 +34224,8 @@ def _hydrabase_reconnect_loop(): logger.error(f"[Hydrabase] Reconnect attempt failed ({_consecutive_failures}): {e}") elif _consecutive_failures == 4: logger.error("[Hydrabase] Reconnect failing repeatedly — suppressing further logs until success") - except Exception: - pass # Don't crash the monitor loop + except Exception as e: + logger.debug("hydrabase monitor loop: %s", e) def _emit_service_status_loop(): """Background thread that pushes service status every 5 seconds.""" diff --git a/webui/static/helper.js b/webui/static/helper.js index fdd4a3ed..6ed334cd 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3432,6 +3432,7 @@ const WHATS_NEW = { '2.4.2': [ // --- post-2.4.1 dev work — entries hidden by _getLatestWhatsNewVersion until the build version bumps --- { date: 'Unreleased — 2.4.2 dev cycle' }, + { title: 'Internal: Stop Swallowing Exceptions Silently', desc: 'github issue #369 (johnbaumb): the codebase had ~300 `except Exception: pass` blocks — and another ~30 bare `except: pass` ones — across web_server.py, every metadata client, every download/import worker, the repair jobs, and most service modules. when one of those paths failed at runtime, the failure was completely invisible: no log line, no telemetry, nothing. you\'d see "downloads stopped working after a few hours" or "enrichment never finishes" and there was nothing to grep for in app.log because the exception had been thrown straight into the void. swept all of them. converted to `except Exception as e: logger.debug("