Skip to content

fix(crossseed): prevent season packs matching episodes - #854

Merged
s0up4200 merged 4 commits into
mainfrom
fix/crossseed-block-pack-from-episode
Dec 27, 2025
Merged

fix(crossseed): prevent season packs matching episodes#854
s0up4200 merged 4 commits into
mainfrom
fix/crossseed-block-pack-from-episode

Conversation

@s0up4200

@s0up4200 s0up4200 commented Dec 24, 2025

Copy link
Copy Markdown
Collaborator

Summary

  • Prevents season pack cross-seeds from matching against single-episode torrents when FindIndividualEpisodes is enabled, avoiding incorrect matches and episode-style renames on pack torrents.
  • Centralizes TV episode/season-pack detection and the forbidden-pair predicate to keep behavior consistent across apply, search, webhook checks, and alignment.
  • Adds coverage for the forbidden pairing and uses a shared reject-reason constant.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented season-pack torrents from being paired with single-episode torrents across search, alignment, renaming, and webhook flows; added TV-aware guards, early rejection and explicit reject reasons so season-vs-episode matches are skipped and logged.
  • Tests

    • Added unit tests validating episode vs. season-pack detection, forbidden pairing rejection, candidate-selection behavior, and propagation of reject reasons.

✏️ Tip: You can customize this high-level summary in your review settings.

Prevents season pack cross-seeds from matching against single-episode
torrents when FindIndividualEpisodes is enabled, avoiding incorrect
matches and episode-style renames on pack torrents.

Centralizes TV episode/season-pack detection and the forbidden-pair
predicate to keep behavior consistent across apply, search, webhook
checks, and alignment.

Adds coverage for the forbidden pairing and uses a shared reject-reason
constant.
@coderabbitai

coderabbitai Bot commented Dec 24, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds TV-aware guards and rejection propagation to prevent season-pack releases from being cross-seeded against single-episode torrents; introduces TV classification helpers and updates matching, alignment, search, and webhook paths plus tests.

Changes

Cohort / File(s) Summary
TV Matching Helpers
internal/services/crossseed/matching.go
Adds isTVEpisode(), isTVSeasonPack(), rejectSeasonPackFromEpisode() and rejectReasonSeasonPackFromEpisode to detect and reject season-pack vs single-episode pairings.
Service Layer Rejections
internal/services/crossseed/service.go
Enforces season-pack/episode rejection in findCandidates, findBestCandidateMatch, SearchTorrentMatches, and CheckWebhook; propagates bestRejectReason and adjusts TV size-ignore logic.
Alignment Logic Updates
internal/services/crossseed/align.go
Adds early rejection in alignCrossSeedContentPaths; replaces previous field checks in shouldRenameTorrentDisplay and shouldAlignFilesWithCandidate with TV-specific guards and an explicit reject check.
Tests: New & Updated
internal/services/crossseed/align_test.go, internal/services/crossseed/matching_forbidden_pairs_test.go, internal/services/crossseed/matching_layout_test.go
Updates expectations for season-pack vs episode case; adds unit tests for TV helpers and a layout test asserting season-pack rejection against episode candidates.
Comments / Docs
internal/services/crossseed/models.go
Expanded/clarifying comments for FindIndividualEpisodes; no structural or API changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

tests

Suggested reviewers

  • Audionut

Poem

🐰 I hop through releases, sniffing each plea,
Season packs and episodes — "No mix!" says me.
Helpers in place, I guard every trail,
Rejecting the mismatched with a twitch of my tail,
Safe torrents hop home, joyful and free. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: preventing season packs from matching episodes in the crossseed service.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/crossseed-block-pack-from-episode

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 788ae2f and 6d05d1a.

📒 Files selected for processing (1)
  • internal/services/crossseed/matching.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.

Applied to files:

  • internal/services/crossseed/matching.go
🔇 Additional comments (4)
internal/services/crossseed/matching.go (4)

23-26: LGTM - Clean TV episode detection helper.

The nil-safe check correctly identifies TV episodes by verifying both series and episode numbers are present.


28-31: LGTM - Clean season pack detection helper.

The nil-safe check correctly distinguishes season packs (series present, no episode number) from individual episodes.


33-35: LGTM - Clear rejection reason constant.

The constant provides a consistent, user-friendly message for the forbidden pairing rejection.


37-52: LGTM - Robust forbidden-pairing predicate.

The rejection logic correctly prevents season packs from being cross-seeded against single-episode torrents when episode-aware matching is enabled. The unidirectional check (new=pack, existing=episode) is appropriate, as the reverse direction (extracting an episode from a pack) remains valid. All edge cases are properly handled through the helper functions' nil-safety.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
internal/services/crossseed/service.go (2)

3500-3511: Hard safety guard in findBestCandidateMatch looks correct; consider refining reason selection

Using rejectSeasonPackFromEpisode(sourceRelease, candidateRelease, true) here is a good “belt and suspenders” safety net so apply paths can’t ever accept season-pack‑from‑episode pairings, regardless of UI settings.

One minor refinement: today the season‑pack/episode rejection reason only wins if bestRejectReason is still empty. If a prior candidate already set a more generic reason, the more specific season‑pack/episode reason is dropped. If you care about surfacing the most informative message, you could reuse the “longest reason wins” logic you already apply for getMatchTypeWithReason:

-       if reject, reason := rejectSeasonPackFromEpisode(sourceRelease, candidateRelease, true); reject {
-           if bestRejectReason == "" {
-               bestRejectReason = reason
-           }
-           continue
-       }
+       if reject, reason := rejectSeasonPackFromEpisode(sourceRelease, candidateRelease, true); reject {
+           if reason != "" && (bestRejectReason == "" || len(reason) > len(bestRejectReason)) {
+               bestRejectReason = reason
+           }
+           continue
+       }

4574-4588: Search‑time forbidden pair filter and TV size‑check exception are coherent

The extra rejectSeasonPackFromEpisode(candidateRelease, sourceRelease, opts.FindIndividualEpisodes) in SearchTorrentMatches correctly treats the Torznab result as “new” and the local torrent as “existing”, so searches won’t surface season‑pack candidates against episode sources when that mode is enabled.

The updated ignoreSizeCheck condition,

ignoreSizeCheck := opts.FindIndividualEpisodes && isTVSeasonPack(sourceRelease) && isTVEpisode(candidateRelease)

matches the intended semantics of allowing large size deltas only when searching from a TV season pack into individual TV episodes. That narrows the previous behavior without breaking the episode‑from‑pack scenario.

One small observability nit: releaseFilteredCount now aggregates both generic release mismatches and these forbidden‑pair rejections. If you ever need to distinguish them in logs/metrics, consider a dedicated counter for forbidden pairs.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ace0101 and 59a64ec.

📒 Files selected for processing (7)
  • internal/services/crossseed/align.go
  • internal/services/crossseed/align_test.go
  • internal/services/crossseed/matching.go
  • internal/services/crossseed/matching_forbidden_pairs_test.go
  • internal/services/crossseed/matching_layout_test.go
  • internal/services/crossseed/models.go
  • internal/services/crossseed/service.go
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.

Applied to files:

  • internal/services/crossseed/models.go
  • internal/services/crossseed/align.go
  • internal/services/crossseed/matching.go
  • internal/services/crossseed/service.go
📚 Learning: 2025-11-28T22:21:20.730Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:2415-2457
Timestamp: 2025-11-28T22:21:20.730Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The determineSavePath function intentionally includes a contentLayout string parameter for future/content-layout branching and API consistency. Its presence is by design even if unused in the current body; do not flag as an issue in reviews.

Applied to files:

  • internal/services/crossseed/align.go
  • internal/services/crossseed/service.go
🧬 Code graph analysis (2)
internal/services/crossseed/matching_forbidden_pairs_test.go (1)
pkg/version/version.go (1)
  • Release (19-34)
internal/services/crossseed/matching_layout_test.go (3)
pkg/releases/parser.go (1)
  • NewDefaultParser (29-31)
pkg/stringutils/normalize.go (1)
  • NewDefaultNormalizer (35-37)
internal/services/crossseed/models.go (1)
  • CrossSeedCandidate (187-199)
🔇 Additional comments (14)
internal/services/crossseed/models.go (1)

36-42: Documentation accurately describes the new TV-aware matching behavior.

The expanded comment clearly explains the dual nature of FindIndividualEpisodes: enabling episode-aware matching for discovery while preventing incorrect season-pack-from-episode cross-seeds. The same documentation is consistently applied across all relevant structs.

internal/services/crossseed/align_test.go (1)

188-191: Test expectation correctly updated to reflect new rejection logic.

The change from True to False for shouldRenameTorrentDisplay(&seasonPack, &episode) correctly aligns with the PR's objective to prevent season pack torrents from being renamed to match episode names. The test coverage for all four combinations (episode→pack, pack→episode, pack→pack, episode→otherPack) is comprehensive.

internal/services/crossseed/matching_layout_test.go (1)

116-149: Comprehensive test for season-pack-to-episode rejection.

The test correctly validates the new rejection behavior by:

  1. Setting up a season pack source with multiple episode files
  2. Providing only a single-episode candidate
  3. Asserting that findBestCandidateMatch returns nil torrent/files and the specific rejection reason

This provides good coverage for the core rejection logic in the matching flow.

internal/services/crossseed/align.go (3)

48-59: Good defense-in-depth guard for forbidden pairings.

The safety check at alignment stage is appropriately defensive - while the rejection should be caught earlier in the flow, having this guard prevents data corruption if earlier checks are bypassed. The debug logging provides useful context for troubleshooting.


525-536: Rename guards correctly implement TV-aware logic.

The two guards work together:

  1. Line 527-529: Prevents episode torrents from being renamed to season pack names (keeps episode named as episode)
  2. Line 532-534: Prevents season pack torrents from being renamed to episode names (forbidden pairing)

The discarded error from rejectSeasonPackFromEpisode is acceptable since the function signature returns an error for future extensibility but currently always returns nil.


539-544: Alignment skip logic correctly updated.

Using the isTVEpisode and isTVSeasonPack helpers makes the condition more readable than the previous explicit field checks. The behavior is correct: when an episode points at season pack files, it uses a subset of the pack's files directly without needing alignment.

internal/services/crossseed/matching.go (2)

23-31: Clean helper functions with proper nil safety.

The isTVEpisode and isTVSeasonPack helpers are well-designed:

  • Nil checks prevent panics when called with uninitialized releases
  • Logic correctly distinguishes episodes (Series > 0 && Episode > 0) from season packs (Series > 0 && Episode == 0)
  • Functions are pure and have no side effects, making them easy to test and use across the codebase

37-52: Rejection logic correctly implements the unidirectional constraint.

The function only rejects the forbidden direction (season pack → episode) while allowing the valid direction (episode → season pack). Key design decisions:

  1. episodeMatching parameter: Allows callers to bypass rejection when the feature is disabled
  2. Unidirectional check: isTVSeasonPack(newR) && isTVEpisode(existingR) specifically catches season packs trying to use episode files
  3. Return values: (reject bool, reason string) allows callers to use just the boolean or include the reason in logs/responses

The documentation clearly explains the purpose and parameters.

internal/services/crossseed/matching_forbidden_pairs_test.go (4)

1-56: Comprehensive test coverage for isTVEpisode.

The test cases cover all relevant scenarios including nil handling, empty releases, non-TV content, season packs, and episodes. Using t.Parallel() at both the parent and subtest levels enables efficient parallel execution.


58-104: Thorough test coverage for isTVSeasonPack.

The test correctly validates that season packs are identified by having Series > 0 and Episode == 0, while other combinations return false.


106-220: Excellent test coverage for rejectSeasonPackFromEpisode.

The test matrix covers:

  • The forbidden pairing in both enabled/disabled states
  • Bidirectional checks (pack→episode vs episode→pack)
  • Same-type pairings (episode→episode, pack→pack)
  • Nil handling for both parameters
  • Non-TV content (movies)
  • Cross-type content (movie vs TV)

This provides high confidence in the rejection logic's correctness.


222-229: Good defensive test for the constant value.

Testing that the constant contains expected substrings ("Season packs" and "single-episode") helps catch accidental modifications to the error message while allowing minor wording adjustments.

internal/services/crossseed/service.go (2)

2226-2231: Season-pack/episode guard in findCandidates is well-placed

Short‑circuiting candidates via rejectSeasonPackFromEpisode(targetRelease, candidateRelease, req.FindIndividualEpisodes) before releasesMatch and file loading is correct and avoids unnecessary work. Argument ordering (new vs existing) is consistent with later call sites.


7400-7406: Webhook check now correctly excludes season‑pack‑from‑episode pairings

The new rejectSeasonPackFromEpisode(incomingRelease, existingRelease, findIndividualEpisodes) guard in CheckWebhook keeps incoming season packs from being treated as matches against existing single‑episode torrents when that mode is enabled, aligning webhook behavior with apply/search logic and preventing misleading “can cross‑seed” signals for those pairs.

@s0up4200
s0up4200 merged commit cfbbc1f into main Dec 27, 2025
3 of 11 checks passed
@s0up4200
s0up4200 deleted the fix/crossseed-block-pack-from-episode branch December 27, 2025 20:59
alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Jan 4, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.11.0` → `v1.12.0` |

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.12.0`](https://github.com/autobrr/qui/releases/tag/v1.12.0)

[Compare Source](autobrr/qui@v1.11.0...v1.12.0)

#### Changelog

##### New Features

- [`202e950`](autobrr/qui@202e950): feat(automations): Add `free_space` condition ([#&#8203;1061](autobrr/qui#1061)) ([@&#8203;Barcode-eng](https://github.com/Barcode-eng))
- [`3b106d6`](autobrr/qui@3b106d6): feat(automations): make conditions optional for non-delete rules and add drag reorder ([#&#8203;939](autobrr/qui#939)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`0684d75`](autobrr/qui@0684d75): feat(config): Add QUI\_\_OIDC\_CLIENT\_SECRET\_FILE env var ([#&#8203;841](autobrr/qui#841)) ([@&#8203;PedDavid](https://github.com/PedDavid))
- [`dac0173`](autobrr/qui@dac0173): feat(config): allow disabling tracker icon fetching ([#&#8203;823](autobrr/qui#823)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`dc10bad`](autobrr/qui@dc10bad): feat(crossseed): add cancel run and opt-in errored torrent recovery ([#&#8203;918](autobrr/qui#918)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cd1fcc9`](autobrr/qui@cd1fcc9): feat(crossseed): add custom category option for cross-seeds ([#&#8203;907](autobrr/qui#907)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d189fe9`](autobrr/qui@d189fe9): feat(crossseed): add indexerName to webhook apply + fix category mode defaults ([#&#8203;916](autobrr/qui#916)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`03a147e`](autobrr/qui@03a147e): feat(crossseed): add option to skip recheck-required matches ([#&#8203;825](autobrr/qui#825)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`edae00a`](autobrr/qui@edae00a): feat(crossseed): add optional hardlink mode for cross-seeding ([#&#8203;849](autobrr/qui#849)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`0938436`](autobrr/qui@0938436): feat(crossseed): add source aliasing for WEB/WEB-DL/WEBRip precheck matching ([#&#8203;874](autobrr/qui#874)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`65f6129`](autobrr/qui@65f6129): feat(crossseed): show failure reasons, prune runs, and add cache cleanup ([#&#8203;923](autobrr/qui#923)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e10fba8`](autobrr/qui@e10fba8): feat(details): torrent details panel improvements ([#&#8203;884](autobrr/qui#884)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6921140`](autobrr/qui@6921140): feat(docs): add Docusaurus documentation site ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6a5a66c`](autobrr/qui@6a5a66c): feat(docs): add Icon and webUI variables to the Unraid install guide ([#&#8203;942](autobrr/qui#942)) ([@&#8203;BaukeZwart](https://github.com/BaukeZwart))
- [`281fce7`](autobrr/qui@281fce7): feat(docs): add local search plugin ([#&#8203;1076](autobrr/qui#1076)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`566de08`](autobrr/qui@566de08): feat(docs): add qui logo, update readme, remove v4 flag ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b83ac5a`](autobrr/qui@b83ac5a): feat(docs): apply minimal.css theme to Docusaurus ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fe6a6df`](autobrr/qui@fe6a6df): feat(docs): improve documentation pages and add support page ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`62a7ad5`](autobrr/qui@62a7ad5): feat(docs): use qui favicon ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`5d124c0`](autobrr/qui@5d124c0): feat(orphan): add auto cleanup mode ([#&#8203;897](autobrr/qui#897)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3172ad9`](autobrr/qui@3172ad9): feat(settings): add log settings + live log stream ([#&#8203;876](autobrr/qui#876)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3c1b34b`](autobrr/qui@3c1b34b): feat(torrents): add "torrent introuvable" to unregistered status ([#&#8203;836](autobrr/qui#836)) ([@&#8203;kephasdev](https://github.com/kephasdev))
- [`afe4d39`](autobrr/qui@afe4d39): feat(torrents): add tracker URL editing for single torrents ([#&#8203;848](autobrr/qui#848)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`76dedd7`](autobrr/qui@76dedd7): feat(torrents): update GeneralTabHorizontal to display limits and improve layout ([#&#8203;1078](autobrr/qui#1078)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`6831c24`](autobrr/qui@6831c24): feat(ui): unify payment options into single dialog ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4dcdf7f`](autobrr/qui@4dcdf7f): feat(web): add local file access indicator to instance cards ([#&#8203;911](autobrr/qui#911)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a560e5e`](autobrr/qui@a560e5e): feat(web): compact torrent details panel ([#&#8203;833](autobrr/qui#833)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`557e7bd`](autobrr/qui@557e7bd): feat: add issue/discussion template ([#&#8203;945](autobrr/qui#945)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8b93719`](autobrr/qui@8b93719): feat: add workflow automation system with category actions, orphan scanner, and hardlink detection ([#&#8203;818](autobrr/qui#818)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Bug Fixes

- [`b85ad6b`](autobrr/qui@b85ad6b): fix(automations): allow delete rules to match incomplete torrents ([#&#8203;926](autobrr/qui#926)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ae06200`](autobrr/qui@ae06200): fix(automations): make tags field condition operators tag-aware ([#&#8203;908](autobrr/qui#908)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ace0101`](autobrr/qui@ace0101): fix(crossseed): detect folder mismatch for bare file to folder cross-seeds ([#&#8203;846](autobrr/qui#846)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`1cc1243`](autobrr/qui@1cc1243): fix(crossseed): enforce resolution and language matching with sensible defaults ([#&#8203;855](autobrr/qui#855)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cefb9cd`](autobrr/qui@cefb9cd): fix(crossseed): execute external program reliably after injection ([#&#8203;1083](autobrr/qui#1083)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`867e2da`](autobrr/qui@867e2da): fix(crossseed): improve matching with size validation and relaxed audio checks ([#&#8203;845](autobrr/qui#845)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4b5079b`](autobrr/qui@4b5079b): fix(crossseed): persist custom category settings in PATCH handler ([#&#8203;913](autobrr/qui#913)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cfbbc1f`](autobrr/qui@cfbbc1f): fix(crossseed): prevent season packs matching episodes ([#&#8203;854](autobrr/qui#854)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c7c1706`](autobrr/qui@c7c1706): fix(crossseed): reconcile interrupted runs on startup ([#&#8203;1084](autobrr/qui#1084)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`7d633bd`](autobrr/qui@7d633bd): fix(crossseed): use ContentPath for manually-managed single-file torrents ([#&#8203;832](autobrr/qui#832)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d5db761`](autobrr/qui@d5db761): fix(database): include arr\_instances in string pool cleanup + remove auto-recovery ([#&#8203;898](autobrr/qui#898)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c73ec6f`](autobrr/qui@c73ec6f): fix(database): prevent race between stmt cache access and db close ([#&#8203;840](autobrr/qui#840)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a40b872`](autobrr/qui@a40b872): fix(db): drop legacy hardlink columns from cross\_seed\_settings ([#&#8203;912](autobrr/qui#912)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e400af3`](autobrr/qui@e400af3): fix(db): recover wedged SQLite writer + stop cross-seed tight loop ([#&#8203;890](autobrr/qui#890)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`90e15b4`](autobrr/qui@90e15b4): fix(deps): update rls to recognize IP as iPlayer ([#&#8203;922](autobrr/qui#922)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8e81b9f`](autobrr/qui@8e81b9f): fix(proxy): honor TLSSkipVerify for proxied requests ([#&#8203;1051](autobrr/qui#1051)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`eb2bee0`](autobrr/qui@eb2bee0): fix(security): redact sensitive URL parameters in logs ([#&#8203;853](autobrr/qui#853)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`40982bc`](autobrr/qui@40982bc): fix(themes): prevent reset on license errors, improve switch performance ([#&#8203;844](autobrr/qui#844)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a8a32f7`](autobrr/qui@a8a32f7): fix(ui): incomplete torrents aren't "Completed: 1969-12-31" ([#&#8203;851](autobrr/qui#851)) ([@&#8203;finevan](https://github.com/finevan))
- [`5908bba`](autobrr/qui@5908bba): fix(ui): preserve category collapse state when toggling incognito mode ([#&#8203;834](autobrr/qui#834)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`25c847e`](autobrr/qui@25c847e): fix(ui): torrents with no creation metadata don't display 1969 ([#&#8203;873](autobrr/qui#873)) ([@&#8203;finevan](https://github.com/finevan))
- [`6403b6a`](autobrr/qui@6403b6a): fix(web): column filter status now matches all states in category ([#&#8203;880](autobrr/qui#880)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`eafc4e7`](autobrr/qui@eafc4e7): fix(web): make delete cross-seed check rely on content\_path matches ([#&#8203;1080](autobrr/qui#1080)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d57c749`](autobrr/qui@d57c749): fix(web): only show selection checkbox on normal view ([#&#8203;830](autobrr/qui#830)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`60338f6`](autobrr/qui@60338f6): fix(web): optimize TorrentDetailsPanel for mobile view and make tabs scrollable horizontally ([#&#8203;1066](autobrr/qui#1066)) ([@&#8203;martylukyy](https://github.com/martylukyy))
- [`aedab87`](autobrr/qui@aedab87): fix(web): speed limit input reformatting during typing ([#&#8203;881](autobrr/qui#881)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`df7f3e0`](autobrr/qui@df7f3e0): fix(web): truncate file progress percentage instead of rounding ([#&#8203;919](autobrr/qui#919)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2fadd01`](autobrr/qui@2fadd01): fix(web): update eslint config for flat config compatibility ([#&#8203;879](autobrr/qui#879)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`721cedd`](autobrr/qui@721cedd): fix(web): use fixed heights for mobile torrent cards ([#&#8203;812](autobrr/qui#812)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`a7db605`](autobrr/qui@a7db605): fix: remove pnpm-workspace.yaml breaking CI ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c0ddc0a`](autobrr/qui@c0ddc0a): fix: use prefix matching for allowed bash commands ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Other Changes

- [`fff52ce`](autobrr/qui@fff52ce): chore(ci): disable reviewer ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`7ef2a38`](autobrr/qui@7ef2a38): chore(ci): fix automated triage and deduplication workflows ([#&#8203;1057](autobrr/qui#1057)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d84910b`](autobrr/qui@d84910b): chore(docs): move Tailwind to documentation workspace only ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`37ebe05`](autobrr/qui@37ebe05): chore(docs): move netlify.toml to documentation directory ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e25de38`](autobrr/qui@e25de38): chore(docs): remove disclaimer ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c59b809`](autobrr/qui@c59b809): chore(docs): update support sections ([#&#8203;1063](autobrr/qui#1063)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b723523`](autobrr/qui@b723523): chore(tests): remove dead tests and optimize slow test cases ([#&#8203;842](autobrr/qui#842)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`662a1c6`](autobrr/qui@662a1c6): chore(workflows): update runners from 4vcpu to 2vcpu for all jobs ([#&#8203;859](autobrr/qui#859)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`46f2a1c`](autobrr/qui@46f2a1c): chore: clean up repo root by moving Docker, scripts, and community docs ([#&#8203;1054](autobrr/qui#1054)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2f27c0d`](autobrr/qui@2f27c0d): chore: remove old issue templates ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`04f361a`](autobrr/qui@04f361a): ci(triage): add labeling for feature-requests-ideas discussions ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f249c69`](autobrr/qui@f249c69): ci(triage): remove needs-triage label after applying labels ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`bdda1de`](autobrr/qui@bdda1de): ci(workflows): add self-dispatch workaround for discussion events ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a9732a2`](autobrr/qui@a9732a2): ci(workflows): increase max-turns to 25 for Claude workflows ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d7d830d`](autobrr/qui@d7d830d): docs(README): add Buy Me a Coffee link ([#&#8203;863](autobrr/qui#863)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`266d92e`](autobrr/qui@266d92e): docs(readme): Clarify ignore pattern ([#&#8203;878](autobrr/qui#878)) ([@&#8203;quorn23](https://github.com/quorn23))
- [`9586084`](autobrr/qui@9586084): docs(readme): add banner linking to stable docs ([#&#8203;925](autobrr/qui#925)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e36a621`](autobrr/qui@e36a621): docs(readme): use markdown link for Polar URL ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9394676`](autobrr/qui@9394676): docs: add frontmatter titles and descriptions, remove marketing language ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ba9d45e`](autobrr/qui@ba9d45e): docs: add local filesystem access snippet and swizzle Details component ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4329edd`](autobrr/qui@4329edd): docs: disclaimer about unreleased features ([#&#8203;943](autobrr/qui#943)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`735d065`](autobrr/qui@735d065): docs: improve external programs, orphan scan, reverse proxy, tracker icons documentation ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`78faef2`](autobrr/qui@78faef2): docs: remove premature tip and fix stat command ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`eaad3bf`](autobrr/qui@eaad3bf): docs: update social card image in Docusaurus configuration ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`02a68e5`](autobrr/qui@02a68e5): refactor(crossseed): hardcode ignore patterns for file matching ([#&#8203;915](autobrr/qui#915)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.11.0...v1.12.0>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.12.0`
- `docker pull ghcr.io/autobrr/qui:latest`

#### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi42OS4yIiwidXBkYXRlZEluVmVyIjoiNDIuNjkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/3060
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant