feat(usenet): auto-repair files whose bodies are missing on playback (NNTP error 430) using DFS as mount. - #358
Open
TheMightyBattleCat wants to merge 1 commit into
Conversation
A Usenet article body can be purged from your providers over time (retention expiry, takedowns) while its header stays alive. When you play a file whose bodies are gone, the read fails with NNTP 430 "article not found" and the file just sits there broken — you have to notice, delete it, and re-search by hand. This detects that during DFS playback and fixes it automatically. When a read hits a permanent 430, the DFS circuit breaker fast-trips (retrying a genuinely missing article is pointless) and escalates: the played file is deleted and the owning Sonarr/Radarr is asked to blocklist the dead grab and re-search for a working copy. The read already proved the body is missing, so there is no redundant re-probe (a re-probe can miss the exact dead segments and wrongly report the file healthy, suppressing the repair). Gated behind Repair.RepairOnPlaybackFailure (requires Enabled + AutoRepair). A per-file/per-entry cooldown prevents repeated escalation of the same file, delete failures are non-fatal so the blocklist + re-search still proceed, and a per-ID delete fallback treats already-missing files as success. The repair actions themselves are mount-agnostic; the automatic playback trigger is wired into the DFS read path, so rclone-mount users get the same repair via the scheduled sweep rather than an immediate playback trigger. Repair progress is tracked per entry, matching how Broken/Probed/ Healthy are counted in the sweep summary. Partially-repaired entries (some but not all files in an entry healed) log the repair action explicitly, so every repair has a traceable log line. Files: - pkg/usenet/usenet.go record + expose permanent per-file failure cause - pkg/manager/stream.go surface the failure cause to the DFS layer - pkg/mount/dfs/vfs/downloaders.go fast-trip breaker on 430; escalate to repair - pkg/manager/repair_sweep.go RepairPlaybackFileNow: delete + blocklist + research - pkg/manager/manager.go per-entry repair cooldown - pkg/arr/content.go non-fatal bulk delete + per-ID delete fallback - internal/config/config.go RepairOnPlaybackFailure toggle
|
Thank you 🙏 I was already looking into that problem myself. I will test out your branch. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
A Usenet article's body can be purged from every configured provider — retention gaps/expiry, takedowns — while its header stays alive. The result is a file that passes header-level health checks but dies the moment someone actually plays it: the read fails with NNTP 430 "article not found", playback stops, and the file just sits there broken until someone notices, deletes it, and re-searches by hand. The person pressing play is usually not the person who can do any of that.
What this adds
When a read through the built-in DFS mount hits a permanent 430, decypharr now treats it as what it is — proof the release is dead — and repairs it on the spot:
Scope is explicit and narrow: the automatic trigger lives entirely in the DFS read path. The repair actions themselves are mount-agnostic, so rclone/WebDAV users get the identical repair via the scheduled sweep — they just don't get the playback-time trigger, and the settings label says so.
Any media repaired on the fly with this feature shows the repaired files in the “View Broken” screen, but they disappear with the “Clean replaced” or "Fix Broken” button (from previous PR). Rclone users have to trigger the sweep manually or wait for their next scheduled run, as they legitimately need repairing.
Safety / fallback behaviour
repair_on_playback_failurerequires repairenabledandauto_repairto be set. It never does anything the sweep's auto-repair wouldn't be allowed to do.Testing
Validated live on my own setup: playing a retention-expired or DCMA’d media (through the DFS mount trips the breaker on the 430, the dead grab is blocklisted and re-searched in Sonarr/Radarr, and the replacement plays. Repeated plays of the same dying file within the cooldown window collapse into a single repair. Transient failures were confirmed to keep taking the normal retry path.
go build ./...andgo vet ./...pass clean.During testing my entire library has been pretty much been sanitised with this feature and the ffprobe on repair sweep (which among other issues, can also triggers a 430 when the body is probed). This with your 100% NZB Import Availability Sample feature combined with the enhanced ffprobe to stop the ridiculous duration media and missing video/audio streams on import has had a huge impact on my library. 72hr sweep windows only see about 4 or 5 genuine failures each time (17k entry library - all usenet).
Unlike most people, I keep my scheduled plex tasks running in a small window over night (I like the intro skip) and when it detects a 430 during plex’s maintenance - that too triggers a replacement on the fly, which is great.
Config / UI
repair_on_playback_failure(bool, default false) on the repair configTrying it out
A multi-arch Docker test build with this change (alongside my other in-flight fixes) is available at
ghcr.io/themightybattlecat/decypharr:usenet-improvements, with the merged source on theusenet-improvementsbranch of my fork. Happy to adjust anything if you'd prefer it shaped differently.