Skip to content

Release of v2.2.0: Sportarr, qBit API_Key, bug fixes - #386

Merged
ManiMatter merged 47 commits into
latestfrom
dev
Sep 30, 2026
Merged

ManiMatter merged 47 commits into
latestfrom
dev

Conversation

@ManiMatter

@ManiMatter ManiMatter commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

New

Fixes

Other

lolimmlost and others added 30 commits May 7, 2026 16:50
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>
…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
Sportarr and others added 17 commits August 4, 2026 11:23
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 lolimmlost left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / #none keyword.
  • The leftover dummy files and the old BREAKING CHANGE ON NOV-1, 2025.md are deleted in this diff, not shipped.
  • README and config example cover qBit api_key, Sportarr and remove_from_queue.
  • This also resolves #378: the qBit api_key support from #367 finally reaches the :latest image.

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_queue option for private_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 timeout override (#346)
  • remove_metadata_missing can detect stuck metadata for non-qBittorrent clients (opt-in) (#364)

Fixes

  • remove_orphans no 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 true or a dict keep their built-in defaults such as max_strikes and message_patterns (#382)
  • Unreachable instances are degraded and retried at startup instead of exiting the whole app (#366)
  • detect_deletions enable/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

@ManiMatter

Copy link
Copy Markdown
Owner Author

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

@ManiMatter
ManiMatter merged commit 77a5a58 into latest Sep 30, 2026
10 checks passed
@lolimmlost

Copy link
Copy Markdown
Collaborator

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,
The release notes are mostly just mark down so you can use regular mark down syntax to get it formatted properly.

@ManiMatter

Copy link
Copy Markdown
Owner Author

What I meant: did you write the markdown yourself or is there a trick that claude or another llm can create that for you?

@lolimmlost

lolimmlost commented Sep 30, 2026 via email

Copy link
Copy Markdown
Collaborator

@ManiMatter

ManiMatter commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner Author

Just realized the build failed. Hotfixing it (because I don't really know any better..) Hope that's ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.