Repository navigation
Release of v2.2.0: Sportarr, qBit API_Key, bug fixes - #386
Conversation
Adds three files under .github/ISSUE_TEMPLATE/ to structure incoming issues and reduce the back-and-forth maintainers currently spend extracting version/config/log details from terse reports. - bug_report.yml: required fields for decluttarr version, deployment method, *arr versions, download client, sanitized config, debug logs, repro steps, and expected vs. actual behavior. Auto-applies the "Bug" label. - feature_request.yml: structured around problem statement, proposed solution, alternatives considered. Auto-applies the "Feature" label. - config.yml: disables blank issues so users must pick a template, and adds two contact links — one to GitHub Discussions for questions/config help (which currently land in the issue tracker and clutter triage), and one to the README's FAQ section that already covers many common problems. Closes part of the chore list in discussion #345 ("Set up Issue templates"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…g template Self-review pass on the templates flagged four gaps that would otherwise be extracted via comments. Adding upfront: - Host OS / architecture dropdown (Linux x86_64, Linux ARM, macOS, Windows, Other) — path-related bugs and qBit issues sometimes behave differently on Windows or ARM (cf. #308 Windows path mappings). - test_run state dropdown — removal-related bugs are very different in test_run=true vs false; capturing this avoids the "wait, were removals actually happening?" round-trip. - Reproduction frequency dropdown (Always / Sometimes / Once / Unknown) — affects triage priority and whether the report is actionable. - Log-sanitization warning: decluttarr already redacts X-Api-Key headers but not ?apikey=... query strings or session cookies, so the description now explicitly tells reporters to scrub those. All four are required fields. Field ordering follows a setup → context → detail narrative. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per @ManiMatter's review on #357: the previous description told reporters to find their version in a log line `Decluttarr <version>` that doesn't actually exist. image_tag and short_commit_id come from env vars set by the Docker build pipeline (only surfaced in the web UI footer once #337 lands), not from any startup log message. Replace with practical instructions for both deployment paths: - Docker users: the image tag they pulled (latest/dev) - Local Python users: short commit hash from git rev-parse --short HEAD Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ManiMatter pointed out that decluttarr already prints a complete configuration dump at startup under the `🛠️ Decluttarr - Settings 🛠️` header (when log level is DEBUG or VERBOSE) — covering image_tag, short_commit_id, all general settings, jobs, instances, and download clients with API keys already masked as `*****`. Asking for a separate "Sanitized config" field is therefore redundant with what reporters will paste in the logs field anyway. Changes: - Drop the "Sanitized config" textarea entirely - Rewrite "Debug-level logs" → "Decluttarr startup banner + debug logs" with the three-step workflow @ManiMatter described: 1. disable jobs not related to the issue 2. set log_level: DEBUG 3. paste everything from the startup banner through the failing cycle - Tweak the version-field description to mention that the banner also contains image_tag / short_commit_id, so reporters can cross-check Net change: 13 fields instead of 14, less duplication, more useful content per report. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ManiMatter pointed out that accepting `latest` / `dev` tags as version values is misleading since those tags move quickly — for reproducible bugs we need the specific commit. Drop the tag aliases, ask for the short commit hash directly, point Docker users at the startup banner's `short_commit_id` and local Python users at `git rev-parse --short HEAD`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chore: Add GitHub issue templates
…ent clients The remove_metadata_missing job only matches the "qBittorrent is downloading metadata" message that qBittorrent surfaces in the *arr queue. Clients such as Transmission or Deluge surface no such message, so a torrent stuck fetching metadata (status "queued", size 0) is never detected and lingers indefinitely (see #57). Add an opt-in, client-agnostic fallback `detect_via_missing_size` (default False, so existing qBittorrent behaviour is unchanged): when enabled, queued items whose size is not yet known (size 0) are also flagged. Reuses the existing max_strikes debounce and blocklist/redownload flow. Adds a QueueManager.filter_missing_size helper, tests for on/off/dedup/partial cases, and README docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…resilience [NEEDS CODE REVIEWER] (time-out related) Fix detect_deletions disable gating and harden timeout handling
[NEEDS CODE REVIEWER] (time-out related) Adding timeout property to instances.
Catch HTTPError in qBit check_connected to prevent app crash on 500 responses (e.g. rdt-client). Treats the error as a disconnected client, skips the cycle, and retries next run. Closes #348
Previously any single instance failing its startup check terminated decluttarr via wait_and_exit(), taking healthy instances down with it and causing container crash-loops on slow servers (#317 follow-up). Now each unit (arr, qBittorrent, SABnzbd) records a readiness state: - Transient failures (timeout, connection, 5xx, unknown) degrade the instance; setup is re-attempted every timer cycle and the instance rejoins automatically (including its detect_deletions watchers). - Definitive config errors (401/403, wrong username/password, bad SABnzbd api key, non-English UI, client version too old) degrade the instance with a per-cycle ERROR + tip; they are not retried since they cannot heal without user action (and retrying a bad password would get the IP banned by qBittorrent). - The app exits when nothing is configured, or when every configured unit has failed definitively - re-checked each cycle, not only at launch. Degraded download clients are skipped everywhere a job would call them (bandwidth checks, obsolete-tagging, bad-file handling) via a ready_only lookup. Removal jobs additionally fail closed: a download whose configured client is degraded is left untouched rather than deleted, since its protection status (protected tag, private/public tracker) cannot be verified while the client is down. Wrong-arr-type and arr-version-too-old keep their existing log-and-continue behavior. Repeated identical setup failures log a single-line skip instead of the full error block each cycle. Also fixes main.py handing the deletion watchers to a throwaway WatcherManager, which left terminate() stopping an instance that owned no observers.
Degrade unreachable instances at startup instead of exiting the whole app
qBittorrent 5.2 added stateless API-key auth (Authorization: Bearer), which lets users avoid storing username/password credentials. This adds an optional `api_key` field to qBittorrent clients and makes it the recommended auth path; username/password remain as a legacy fallback for qBit < 5.2. - QbitClient accepts `api_key`; a new `_auth_kwargs()` helper returns the Bearer header in key mode or the SID cookie in password mode, used at every authenticated request site. - refresh_cookie() and check_qbit_reachability() branch for key mode: login is skipped (qBit rejects /auth/login under key auth) and reachability probes /app/version with the Bearer header instead. A bad key or a <5.2 server both surface a clear tip and degrade instead of crashing. - If both api_key and username/password are set, the key wins (mirrors qBit), logged once at init. - Redact `Authorization` in sanitize_kwargs so the token never leaks in DEBUG logs. - Docs (config example, README) present api_key as recommended, creds as legacy. Tests cover header-vs-cookie selection, login skip, precedence, and the 403 degrade path.
Support qBittorrent 5.2 API key authentication
…nt-agnostic [NEEDS CODE REVIEWER] feat(remove_metadata_missing): detect stuck metadata for non-qBittorrent clients (opt-in)
[NEEDS CODE REVIEWER] fix: Clarify malformed environment YAML errors
[NEEDS CODE REVIEWER] fix: Skip stale queue items during unmonitored checks
…ueue Fix metadata-missing detection for full-only queue items
Added hints for checking the correct README version and noted breaking changes in Decluttar V2.
Updated the bug report template to clarify README version references.
Clarify README version in bug report template
Adds Sportarr (https://github.com/Sportarr/Sportarr), a sports event manager exposing a Sonarr-v3-compatible API, as a supported instance type. It rides the sonarr-shaped constants (api/v3, series/episode keys, EpisodeSearch/RefreshSeries commands, includeUnknownSeriesItems) and joins sonarr in every per-type conditional: instance loading, refresh plumbing, deletion watchers, search logging, and startup rejoin. README and config example gain SPORTARR/sportarr blocks. Full test suite passes (292) and ruff reports no new findings on the touched files.
Private-tracker torrents currently have no handling option that clears the *arr queue without also deleting the download. "remove" passes removeFromClient=true, which stops seeding and can trigger a hit-and-run penalty; "obsolete_tag" only tags the torrent and leaves the queue entry in place indefinitely, so the *arr keeps waiting on a download that will never import. remove_from_queue is "remove" minus the client deletion: the queue entry is cleared and blocklisted per the job's existing blocklist policy, while the torrent stays in the client for an external tool (e.g. qbit_manage) to retire on the tracker's terms. remove_queue_item() gains a remove_from_client keyword, defaulting to True so existing behaviour is unchanged.
PR #333 added JobParams.__bool__ (truthiness = enabled). Jobs._set_job_settings uses `if job:` to decide whether to reuse the pre-built defaults object for a job, and those defaults objects start with enabled=False, so the check is now always false. Every job configured as a bare `true` (or as a dict) is replaced by a fresh JobParams carrying only the keys the user typed: remove_failed_imports: true -> no message_patterns -> AttributeError ('JobParams' object has no attribute 'message_patterns') remove_stalled: true -> no max_strikes -> StrikesHandler skipped, remove_slow: {min_speed: 25} i.e. removal on first sight instead of after 3 strikes Check for `is not None` instead, which is what the code meant before __bool__ existed. Adds regression tests for bool true/false and dict merging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A removal that does not take effect permanently disables every future removal of that download. tracker.deleted gates every removal (removal_handler.py:18) and is appended unconditionally (line 32) without checking whether the removal worked, yet it is only ever cleared by Tracker.reset() when the *arr queue is completely empty. A download that cannot be removed keeps the queue non-empty, so the entry is never cleared and that download stays suppressed for the life of the process. Observed on v2.1.0 as a stuck torrent reaching 74 strikes against max_strikes 5 over ~19 hours with zero removals and zero blocklist entries, and no log line explaining it. Prune the tracker at the start of each run using the full queue that _queue_has_items() already fetches: if a download recorded as deleted is still in the queue, the removal did not take effect, so it becomes eligible again. Entries for downloads that really are gone are kept, so the cross-cycle reader in strikes_handler stays correct. obsolete_tag handling deliberately leaves the download in the queue, so queue presence does not disprove it. Tagged ids are recorded separately in Tracker.obsolete_tagged and exempted from the prune; without this an obsolete-tagged torrent is re-tagged on every cycle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- compare the full and normal queue by item id instead of whole records; sizeleft/timeleft of an active download change between the two fetches, so any progressing download was flagged as an orphan and deleted (#322) - regression test
[Bug] Fix remove_orphans deleting active downloads (#322)
…ults [Bug] Fix jobs configured as bare true/dict losing their built-in defaults (max_strikes, message_patterns)
Re-arm removals for downloads that never left the queue
[NEEDS CODE REVIEWER] feat: Add Sportarr support
#383 added this assertion before remove_queue_item gained the remove_from_client keyword, so it fails once both are on dev. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rK7uZPvqGrze3sxdhVnGw
Add `remove_from_queue` tracker handling mode
lolimmlost
left a comment
There was a problem hiding this comment.
Approving. Reviewed the release diff (latest at v2.1.0 vs dev at 08f02e5):
- Contents are exactly current
dev: 15 PRs since v2.1.0. CI is green including the Docker build, and the full suite passes locally (306). - Merging will tag v2.2.0 as intended: the tag action defaults to a minor bump and none of the commits since v2.1.0 contain a
#major/#patch/#nonekeyword. - The leftover dummy files and the old
BREAKING CHANGE ON NOV-1, 2025.mdare deleted in this diff, not shipped. - README and config example cover qBit
api_key, Sportarr andremove_from_queue. - This also resolves #378: the qBit
api_keysupport from #367 finally reaches the:latestimage.
Suggested edit: the release notes undersell this one, in particular the remove_orphans fix, since users lost data to that bug. Something like:
New
- Sportarr support (#377)
- qBittorrent 5.2+ API key authentication (#367)
- New
remove_from_queueoption forprivate_tracker_handling/public_tracker_handling: clears the *arr queue entry (and blocklists per the job) but keeps the torrent seeding in the client (#381) - Per-instance
timeoutoverride (#346) remove_metadata_missingcan detect stuck metadata for non-qBittorrent clients (opt-in) (#364)
Fixes
remove_orphansno longer deletes actively downloading items (#385, fixes #322)- A removal that did not take effect no longer blocks all future removals of that download (#383)
- Jobs configured as a bare
trueor a dict keep their built-in defaults such asmax_strikesandmessage_patterns(#382) - Unreachable instances are degraded and retried at startup instead of exiting the whole app (#366)
detect_deletionsenable/disable gating fixed and timeout handling hardened (#333)- Stale queue items are skipped during unmonitored checks (#371)
- Metadata-missing detection covers items only present in the full queue (#373)
- Clearer errors for malformed environment YAML (#372)
Other
|
Fully agree. Love the review. For my education, how did you create the updated release notes? Did an LLM help you or all by hand? Would love to learn how you did it if there‘s a smart trick |
Hey mani, |
|
What I meant: did you write the markdown yourself or is there a trick that claude or another llm can create that for you? |
|
Yeah I asked Claude to pull the actual commits numbers to the Mark down.
…On Wed, Sep 30, 2026 at 1:02 PM ManiMatter ***@***.***> wrote:
*ManiMatter* left a comment (ManiMatter/decluttarr#386)
<#386 (comment)>
What I meant: did you write the markdown yourself or is there a trick that
claude or another llm can create that for you?
—
Reply to this email directly, view it on GitHub
<#386?email_source=notifications&email_token=ADBC4Q4N6MBR2IPRVSXU5K35RVRFPA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOJRHA3DSMRSGMY2M4TFMFZW63VQOJSXM2LFO5PXEZLROVSXG5DFMSSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5918692231>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADBC4Q2XGFH6XHUB6DBN4YT5RVRFPAVCNFSNUABFKJSXA33TNF2G64TZHM3DSOBVHA2TKMRRHNEXG43VMU5TKNRVGA3DMNBTGU32C5QC>
.
You are receiving this because your review was requested.Message ID:
***@***.***>
|
|
Just realized the build failed. Hotfixing it (because I don't really know any better..) Hope that's ok. |
New
remove_from_queueoption forprivate_tracker_handling/public_tracker_handling: clears the *arr queue entry (and blocklists per the job) but keeps the torrent seeding in the client (Addremove_from_queuetracker handling mode #381)timeoutoverride ([NEEDS CODE REVIEWER] (time-out related) Adding timeout property to instances. #346)remove_metadata_missingcan detect stuck metadata for non-qBittorrent clients (opt-in) ([NEEDS CODE REVIEWER] feat(remove_metadata_missing): detect stuck metadata for non-qBittorrent clients (opt-in) #364)Fixes
remove_orphansno longer deletes actively downloading items ([Bug] Fix remove_orphans deleting active downloads (#322) #385, fixes REMOVE_ORPHANS deletes valid downloads immediately upon completion (Race Condition?) #322)trueor a dict keep their built-in defaults such asmax_strikesandmessage_patterns([Bug] Fix jobs configured as bare true/dict losing their built-in defaults (max_strikes, message_patterns) #382)detect_deletionsenable/disable gating fixed and timeout handling hardened ([NEEDS CODE REVIEWER] (time-out related) Fix detect_deletions disable gating and harden timeout handling #333)Other