Skip to content

fix(media_cleaner): fix watch data matching, collection creation, and death row logging#229

Merged
rfsbraz merged 2 commits intomainfrom
fix/leaving-soon-reliability
Feb 20, 2026
Merged

fix(media_cleaner): fix watch data matching, collection creation, and death row logging#229
rfsbraz merged 2 commits intomainfrom
fix/leaving-soon-reliability

Conversation

@rfsbraz
Copy link
Copy Markdown
Owner

@rfsbraz rfsbraz commented Feb 20, 2026

Summary

  • title_and_year_match bug: Removed inverted year != check that caused exact year matches to fail. The fallback watch data matcher only matched when years differed by exactly 1, making same-year items appear unwatched and eligible for deletion.
  • Collection creation crash: get_or_create_collection now returns None instead of crashing when called with no items (happens when Plex auto-removes an empty collection after all its items are deleted).
  • Death row logging: Enhanced log message to explain why items were filtered out (protected by thresholds, exclusions, or watch activity since tagging).

Test plan

  • test_exact_year_match_should_find_watch_data - red/green verified regression
  • test_off_by_one_year_still_matches - existing ±1 tolerance preserved
  • test_year_off_by_two_rejects - years 2+ apart still rejected
  • test_different_title_rejects - different titles rejected
  • test_none_year_on_plex_item_rejects / test_none_year_on_history_rejects - None years handled
  • test_collection_not_created_when_no_items - graceful None return
  • test_existing_collection_cleared_when_no_items - existing collection cleared
  • test_death_row_logging_shows_filtered_count - enhanced log message
  • Full suite: 629 passed, 0 failed

Refs #227

… death row logging

Fix title_and_year_match rejecting exact year matches due to inverted
year != check. The fallback watch data matcher only matched when years
differed by exactly 1, causing items with exact year matches to appear
unwatched and become deletion candidates.

Fix get_or_create_collection crashing when called with no items after
Plex auto-removes an empty collection. Return None instead of calling
createCollection with an empty items list.

Improve death row logging to explain why items were filtered out
(thresholds, exclusions, or watch activity since tagging).

Refs #227
@github-actions
Copy link
Copy Markdown
Contributor

🐳 A Docker image for this PR will be available after the build completes:

docker run -e LOG_LEVEL=DEBUG --rm \
  -v ./config:/config \
  -v ./logs:/config/logs \
  ghcr.io/rfsbraz/deleterr:pr-229

Note: This image is built for linux/amd64 only. The image is pushed to GHCR (not Docker Hub) for PR testing.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes multiple issues in the “death row / leaving_soon” flow by correcting fallback watch-history matching, preventing Plex collection creation crashes when there are no items, and improving the log output when previously-tagged items are no longer eligible for deletion.

Changes:

  • Fix title_and_year_match so same-year history matches succeed (while keeping ±1-year tolerance) and add regression tests.
  • Make leaving_soon collection updates resilient to empty item sets by allowing get_or_create_collection to return None and clearing existing collections when appropriate.
  • Enhance death-row logging to include how many items were filtered out and add tests for the new message.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/media_cleaner.py Pass items into collection creation, handle None return, and clear existing collections when no items remain.
app/modules/plex.py Update get_or_create_collection to accept optional items and skip creation when empty.
app/deleterr.py Add filtered-out count to death-row logging.
tests/test_media_cleaner_helpers.py Add regression tests for title_and_year_match same-year and boundary cases.
tests/test_leaving_soon.py Update existing expectations for new collection-creation signature and add edge-case + logging tests.
tests/modules/test_plex.py Expand Plex module tests for collection creation with/without items.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/modules/plex.py
Comment on lines +48 to 58
def get_or_create_collection(self, library: Any, name: str, items: Optional[list] = None) -> Optional[Any]:
"""Get existing collection or create a new one.

Args:
library: The Plex library section.
name: The name of the collection.
items: Optional list of items to create the collection with.

Returns:
The collection object.
The collection object, or None if it doesn't exist and no items to create with.
"""
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

PlexMediaServer.get_or_create_collection changes the abstract interface by adding an items kwarg and allowing None returns, but BaseMediaServer.get_or_create_collection still documents/declares a 2-arg method that always returns a collection. This divergence makes the interface contract unclear for other media server implementations and for typed call sites. Update BaseMediaServer (and any other implementations) to match the new signature/semantics, or keep the old signature and handle creation/empty-items behavior internally without changing the interface.

Copilot uses AI. Check for mistakes.
Comment thread app/deleterr.py
logger.info(
f"Found {len(death_row_plex_items)} items in leaving_soon, "
f"{len(items_to_delete)} still match deletion criteria"
+ (f" ({filtered_out} protected by thresholds, exclusions, or watch activity since tagging)" if filtered_out > 0 else "")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

filtered_out is computed as the count of death-row items that are not in the intersection with current deletion candidates, but the log message states they are "protected by thresholds, exclusions, or watch activity". In practice this difference can also include items that can’t be mapped back to Radarr/Sonarr candidates (e.g., ID/title mismatches, items no longer present), so the message can be misleading. Consider wording it as "no longer match deletion criteria (e.g., thresholds/exclusions/recent watch activity)" or similar, without attributing a definitive cause.

Suggested change
+ (f" ({filtered_out} protected by thresholds, exclusions, or watch activity since tagging)" if filtered_out > 0 else "")
+ (
f" ({filtered_out} no longer match deletion criteria "
f"(e.g., thresholds, exclusions, or recent watch activity) since tagging)"
if filtered_out > 0
else ""
)

Copilot uses AI. Check for mistakes.
…ature

The integration test was asserting the old call signature without the
items kwarg, causing CI failure.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@rfsbraz rfsbraz merged commit b4c426d into main Feb 20, 2026
7 of 8 checks passed
@rfsbraz rfsbraz deleted the fix/leaving-soon-reliability branch March 10, 2026 20:08
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.

2 participants