Skip to content

fix: remove orphaned media entries when all requests are deleted#2880

Closed
Kenshin9977 wants to merge 1 commit intoseerr-team:developfrom
Kenshin9977:fix/cleanup-orphaned-media
Closed

fix: remove orphaned media entries when all requests are deleted#2880
Kenshin9977 wants to merge 1 commit intoseerr-team:developfrom
Kenshin9977:fix/cleanup-orphaned-media

Conversation

@Kenshin9977
Copy link
Copy Markdown
Contributor

@Kenshin9977 Kenshin9977 commented Apr 14, 2026

Description

When a user deletes the last request associated with a media entry, the media record is left orphaned in the database with status UNKNOWN. This prevents users from submitting a new request for the same title since Seerr still treats it as an existing item.

handleRemoveParentUpdate() now removes the media entry entirely when no requests remain and the media is not in an active state (AVAILABLE, PARTIALLY_AVAILABLE, or PROCESSING). Media that is already available on disk is never removed.

Also aligns the status-reset branch with the same ACTIVE_STATUSES set to avoid resetting PROCESSING or PARTIALLY_AVAILABLE media to UNKNOWN.

How Has This Been Tested?

Manual testing on a self-hosted instance with orphaned media entries. Verified:

  • Deleting the last request on non-available media removes the media entry
  • Re-requesting the same title works after deletion
  • Media with status AVAILABLE is preserved
  • Media with remaining requests (standard or 4k) is not affected

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy)
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

This pull request was authored with AI assistance.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of orphaned media by implementing proper cleanup when no active requests remain.
  • Refactor

    • Optimized request status checking to reduce redundant scans.
    • Enhanced status tracking logic for media items.

@Kenshin9977 Kenshin9977 requested a review from a team as a code owner April 14, 2026 19:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0e7c4001-1e4f-44ac-97a1-a861fbcb6f8f

📥 Commits

Reviewing files that changed from the base of the PR and between 8f0006c and 75177a2.

📒 Files selected for processing (1)
  • server/subscriber/MediaRequestSubscriber.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/subscriber/MediaRequestSubscriber.ts

📝 Walkthrough

Walkthrough

Added a static ACTIVE_STATUSES set to MediaRequestSubscriber and refactored handleRemoveParentUpdate to compute hasStandardRequests / has4kRequests once, change status-update checks to compare against ACTIVE_STATUSES, and delete orphaned Media via manager.remove(fullMedia) when no requests remain and parent statuses are not active.

Changes

Cohort / File(s) Summary
Media Request Subscriber
server/subscriber/MediaRequestSubscriber.ts
Added MediaRequestSubscriber.ACTIVE_STATUSES: Set<MediaStatus>. Refactored handleRemoveParentUpdate to precompute hasStandardRequests and has4kRequests, updated needsStatusUpdate / needs4kStatusUpdate to use those flags and compare against ACTIVE_STATUSES, and added deletion path that calls manager.remove(fullMedia) when media is orphaned and parent status is inactive.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • gauthier-th
  • fallenbagel
  • 0xSysR3ll

Poem

🐇 I hopped through the code with a twitch and a twitch,
Cached checks in my pouch, cleared out the old glitch.
No lonely media left in the night,
ACTIVE_STATUSES keep statuses right —
A tidy repo, and a happy delight. 🎉

🚥 Pre-merge checks | ✅ 2
✅ 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 'fix: remove orphaned media entries when all requests are deleted' directly and clearly summarizes the main change: orphaned media entries are now removed when all requests are deleted.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/subscriber/MediaRequestSubscriber.ts`:
- Around line 45-49: The reset branch that computes the new status for the
"no-request" side must honor the ACTIVE_STATUSES set instead of only preserving
MediaStatus.AVAILABLE; update the logic in MediaRequestSubscriber.ts so when
computing the new status for the no-request side you only set it to
MediaStatus.UNKNOWN if the current status is not in ACTIVE_STATUSES (i.e.,
preserve MediaStatus.PARTIALLY_AVAILABLE and MediaStatus.PROCESSING), and adjust
the condition that currently checks for equality with MediaStatus.AVAILABLE to
use ACTIVE_STATUSES.has(currentStatus) instead; add regression tests that delete
the last request for media in PROCESSING and PARTIALLY_AVAILABLE states to
ensure they do not become UNKNOWN.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59e51cee-d814-4a31-98b7-b7b25a1b70f2

📥 Commits

Reviewing files that changed from the base of the PR and between 15b3109 and 8f0006c.

📒 Files selected for processing (1)
  • server/subscriber/MediaRequestSubscriber.ts

Comment thread server/subscriber/MediaRequestSubscriber.ts
@Kenshin9977 Kenshin9977 force-pushed the fix/cleanup-orphaned-media branch from 8f0006c to d12a9d6 Compare April 14, 2026 19:36
When a user deletes the last request associated with a media entry,
the media record was left in the database with status UNKNOWN. This
orphaned entry prevented users from submitting a new request for
the same title, as Seerr still treated it as an existing item.

Now, `handleRemoveParentUpdate()` checks whether the media has any
remaining requests and whether it is in an active state (AVAILABLE,
PARTIALLY_AVAILABLE, or PROCESSING). If no requests remain and the
media is not active, the entry is removed entirely, allowing users
to re-request the title cleanly.

Media that is already available on disk is never removed — only
genuinely orphaned entries are cleaned up.

> This pull request was authored with AI assistance.
@Kenshin9977 Kenshin9977 force-pushed the fix/cleanup-orphaned-media branch from d12a9d6 to 75177a2 Compare April 14, 2026 19:38
@fallenbagel
Copy link
Copy Markdown
Collaborator

Hey, thanks for the PR but this is already handled on develop.

When a request is deleted via the request delete endpoint, requestRepository.remove() fires the TypeORM afterRemove subscriber, which calls handleRemoveParentUpdate(). That method loads the media with all the remaining requests, and checks if there are no standard/4K requests left, and resets the status to UNKNOWN if the media isn't AVAILABLE. This is exactly what lets users re-request the title.

Additionally, #2478 already hardened which also fixes the exact issue you mention as now when the media delete endpoint is hit, it preserves the blocklisted media by calling resetServiceData() instead of hard-deleting the row and specifically because the cascade: ['insert', 'remove'] on the requests relation would cascade-delete associated blocklist entries.

There are also a couple of concerns with the changes here as well:

  1. Hard-deleting the media row (manager.remove(fullMedia)) is destructive. This is a big problem. It throws away seasons, external service IDs, and other metadata that resetting to UNKNOWN preserves. It would also cascade-delete any associated blocklist entries, which is the exact bug fix: preserve blocklist on media deletion & optimise watchlist-sync #2478 fixed.

  2. The ACTIVE_STATUSES guard would actually make things worse too by skipping PROCESSING and PARTIALLY_AVAILABLE, it would prevent the status reset to UNKNOWN when the last request is deleted. If there are no requests left, there's nothing to process, so leaving media stuck in PROCESSING is the exact orphaned state users will complain about.

Please test on develop first as this should already be working correctly already. I've tested it myself and have also written a supertest test case that verifies this behaviour. As it stands, these changes would actually break the existing logic rather than fix it. If you are hitting a specific case where media gets stuck after request deletion on develop, please open a GitHub issue with reproduction steps so we can investigate properly then we can decide on how to go about it.

But as it stands, this PR introduces regressions in the existing logic for a problem that should already be fixed on develop, so I'll be closing it.

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