From 9de133244ebd271d0a97eba474501076cbbef932 Mon Sep 17 00:00:00 2001 From: deucebucket Date: Wed, 18 Feb 2026 18:01:40 -0600 Subject: [PATCH 1/2] Fix #160: Rate-limited batches no longer trigger false exhaustion process_queue now returns (-1, 0) when rate-limited instead of (0, 0), so the Layer 4 loop can distinguish "rate limited" from "nothing to process". The worker also checks circuit breaker state before counting empty batches toward the 3-strike exhaustion rule. Books that are identifiable but temporarily blocked by rate limits will no longer be permanently marked as "all processing layers exhausted". --- library_manager/pipeline/layer_ai_queue.py | 2 +- library_manager/worker.py | 26 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/library_manager/pipeline/layer_ai_queue.py b/library_manager/pipeline/layer_ai_queue.py index bae9c70..7b86ec3 100644 --- a/library_manager/pipeline/layer_ai_queue.py +++ b/library_manager/pipeline/layer_ai_queue.py @@ -100,7 +100,7 @@ def process_queue( allowed, calls_made, max_calls = check_rate_limit(config) if not allowed: logger.warning(f"Rate limit reached: {calls_made}/{max_calls} calls. Waiting...") - return 0, 0 + return -1, 0 # Signal rate-limited (distinct from 0,0 = nothing to process) # Check if AI verification is enabled (before opening connection) if not config.get('enable_ai_verification', True): diff --git a/library_manager/worker.py b/library_manager/worker.py index 1e63a0f..9e77094 100644 --- a/library_manager/worker.py +++ b/library_manager/worker.py @@ -391,7 +391,33 @@ def process_all_queue( # At this point, we're trusting folder names as a last resort processed, fixed = process_queue(config, verification_layer=4) + # Issue #160: processed == -1 means rate-limited, NOT "nothing to process" + # Don't count rate-limited batches toward the 3-strike exhaustion rule + if processed == -1: + logger.info("Batch skipped due to rate limiting - not counting toward exhaustion") + _processing_status["current"] = "Rate limited, waiting for cooldown..." + _processing_status["last_activity"] = "Waiting for rate limit cooldown" + _processing_status["last_activity_time"] = time.time() + time.sleep(30) + continue + if processed == 0: + # Check if AI providers are circuit-broken before counting as empty + # If providers are unavailable, this isn't a real "empty" result + ai_provider = config.get('ai_provider', 'gemini') + providers_to_check = [ai_provider] + if ai_provider != 'bookdb': + providers_to_check.append('bookdb') + any_circuit_open = any(is_circuit_open(p) for p in providers_to_check) + + if any_circuit_open: + logger.info(f"AI providers circuit-broken ({', '.join(p for p in providers_to_check if is_circuit_open(p))}) - waiting for recovery, not counting toward exhaustion") + _processing_status["current"] = "AI provider cooling down, waiting..." + _processing_status["last_activity"] = "Waiting for circuit breaker recovery" + _processing_status["last_activity_time"] = time.time() + time.sleep(30) + continue + conn = get_db() c = conn.cursor() c.execute('SELECT COUNT(*) as count FROM queue') From f494cf936a3005a06b8508648989573e4ef10d08 Mon Sep 17 00:00:00 2001 From: deucebucket Date: Wed, 18 Feb 2026 18:28:17 -0600 Subject: [PATCH 2/2] Fix #160: Bump version to beta.130, update docs - Version badge in README matches APP_VERSION - CHANGELOG entry for rate limit exhaustion fix - README Recent Changes updated --- CHANGELOG.md | 14 ++++++++++++++ README.md | 7 ++++++- app.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c424e31..c6592ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to Library Manager will be documented in this file. +## [0.9.0-beta.130] - 2026-02-18 + +### Fixed + +- **Issue #160: Rate-limited batches no longer trigger false exhaustion** - Layer 4 processing + now distinguishes between "genuinely unidentifiable books" and "AI providers temporarily + unavailable." Rate-limited batches return a distinct signal (`-1`) and are not counted toward + the 3-strike exhaustion rule. When circuit breakers are open on AI providers, the worker waits + for recovery instead of marking books as "all processing layers exhausted." Previously, books + that were perfectly identifiable could be permanently marked as failed if providers were + rate-limited during processing. + +--- + ## [0.9.0-beta.129] - 2026-02-18 ### Changed diff --git a/README.md b/README.md index 10b1db6..f9901d8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **Smart Audiobook Library Organizer with Multi-Source Metadata & AI Verification** -[![Version](https://img.shields.io/badge/version-0.9.0--beta.129-blue.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-0.9.0--beta.130-blue.svg)](CHANGELOG.md) [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/deucebucket/library-manager) [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE) @@ -16,6 +16,11 @@ ## Recent Changes (stable) +> **beta.130** - **Fix: Rate-Limited Batches No Longer Trigger False Exhaustion** (Issue #160) +> - **Rate-limited batches skipped** - When AI providers are rate-limited, batches are no longer counted toward the 3-strike "all processing layers exhausted" rule +> - **Circuit breaker awareness** - Layer 4 now waits for providers to recover instead of permanently marking identifiable books as failed +> - **Distinct signal for rate limiting** - `process_queue` returns `-1` (rate-limited) vs `0` (genuinely empty) so the worker can react correctly + > **beta.129** - **UI: Feedback Widget Moved to Nav Bar** (Issue #159) > - **Bug icon in nav bar** - Feedback/bug report button moved from floating bottom-right circle to a consistent bug icon in the top navigation bar > - **No more overlapping buttons** - Eliminates confusing dual floating buttons on the dashboard page diff --git a/app.py b/app.py index d57ded2..87d7e48 100644 --- a/app.py +++ b/app.py @@ -11,7 +11,7 @@ - Multi-provider AI (Gemini, OpenRouter, Ollama) """ -APP_VERSION = "0.9.0-beta.129" +APP_VERSION = "0.9.0-beta.130" GITHUB_REPO = "deucebucket/library-manager" # Your GitHub repo # Versioning Guide: