Repository navigation
fix(arrs): never remove a category folder through the arr or metadata delete paths - #1056
Merged
Merged
Conversation
… delete paths kipsilabs#1034 guards recursive deletes in RemoveFile, but several paths delete without going through it, or make an arr delete a folder itself. When a queue item or a storage path names a category folder (as the history did in kipsilabs#1013), each of these acts on the whole category: - The scanner's queue fallback (failRadarrQueueItemByPath, failSonarrQueueItemByPath) picks the queue item for a broken file by exact path, path prefix or final name. A queue item whose output path is the category folder matches every file in it, and removing it with RemoveFromClient makes the arr delete that folder. Radarr's prefix match also had no component boundary (Show.S01E0 matched Show.S01E02). - The stuck-queue cleanup and the import-failure give-up remove queue items with RemoveFromClient=true: deleteStarrQueue for Radarr, Lidarr and Readarr, the pending removal for Sonarr and Whisparr, and the Sportarr native delete. - The arr webhook deletes the metadata of every file an Upgrade lists. - The stremio cleanup deletes an expired item's storage path with DeleteDirectory. config.IsCategoryRootVirtualPath and IsCategoryRootDownloadPath name the folders that hold many jobs' output: the mount root, complete_dir, the corrupted folder, and a known category (a SABnzbd category name or dir, the Default dir, or an arr instance's category). A release folder of a job without a category also sits directly under complete_dir, so an unknown name there is not a root. A path an arr reports outside mount_path is judged by folder names, and backslashes count as separators for Windows arrs. With that: - the queue fallback skips an item whose output is a category root (with a warning) and matches a prefix only on a path-component boundary; - the cleanup paths still remove and blocklist such an item, but with RemoveFromClient=false. Sportarr's native delete always removes from the client, so such an item is skipped there; - the webhook refuses a listed path that is a category root; - the stremio cleanup refuses a category-root storage path and only drops the queue entry, so it does not retry every run. An empty output path is unchanged: with no path the arr has nothing to delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The health API (single and bulk delete with delete_meta) and the zombie cleanup in the health worker logged only failed metadata deletes. Log each successful one at info, the API ones with the caller's address, so a mass delete outside RemoveFile can be traced afterwards. Import rollback stays at debug: it only removes files the failed import itself just wrote. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… roots
A category dir with a slash, such as "tv/uhd", was only recognised
inside mount_path. For a path an arr reports from outside mount_path
the fallback compared the last folder name with a known category and
required the folder just above it to be named like complete_dir, so
/data/complete/tv/uhd was not a root and could still be removed with
RemoveFromClient. The folder above a nested category ("complete/tv")
was not a root anywhere, although it holds that category too.
Category dirs are now compared relative to complete_dir as a whole
path suffix, and a folder above a known category dir counts as a root,
both for metadata paths and for paths an arr reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Follow-up to #1029 / #1034.
#1034 guards recursive deletes in
RemoveFile. Several other paths delete without going throughRemoveFile, or ask an arr to delete a folder itself. When a queue item or a stored storage path names a category folder (as the SABnzbd history did in #1013, see also #1054), each of them acts on the whole category:failRadarrQueueItemByPath,failSonarrQueueItemByPath) picks the queue item for a broken file by exact path, path prefix or final name. A queue item whose output path is the category folder matches every file in it, and removing it withRemoveFromClientmakes the arr delete that folder. Radarr's prefix match also has no component boundary, soShow.S01E0matchesShow.S01E02.RemoveFromClient=true:deleteStarrQueuefor Radarr, Lidarr and Readarr, the pending removal for Sonarr and Whisparr, and the Sportarr native delete.Upgradelists.DeleteDirectory.Changes
config.IsCategoryRootVirtualPathandIsCategoryRootDownloadPathname the folders that hold many jobs' output: the mount root,complete_dir, the corrupted folder, a known category (a SABnzbd category name or dir, the Default dir, or an arr instance's category), and a folder above a nested category dir (tvfortv/uhd). A path an arr reports outsidemount_pathis judged by its trailing folder names: the category dir relative tocomplete_dir, preceded by a folder named likecomplete_dir. Backslashes count as separators, for Windows arrs.RemoveFromClient=false. For Sonarr this is in the pending removal, which every Sonarr removal goes through. Sportarr's native delete always removes from the client, so such an item is skipped there.RemoveFilecould not be traced afterwards.An empty output path is unchanged: with no path the arr has nothing to delete.
How to reproduce
Have a Radarr or Sonarr queue item from altmount whose
outputPathis<mount>/complete/<category>(what the history reported in #1013), and a broken file under that category that the scanner repairs. The queue fallback matches the item by prefix and sendsDELETE /api/v3/queue/{id}?removeFromClient=true&blocklist=true, and the arr then deletes<mount>/complete/<category>. The new scanner tests do exactly this against a stub arr.Why a separate predicate from
isDeleteGuardRootIn
RemoveFile, #1034 makes a delete of any existing folder directly undercomplete_dira no-op (isDeleteGuardRoot). The files inside it can still be deleted one by one. These paths are different: they act on one job's output as a whole, by asking the arr to remove it from the client or by deleting its metadata. A job without a category is stored directly undercomplete_dir, so with that rule its own release folder would count as a root, and the arr would never be asked to remove it from the client. So these paths count onlycomplete_diritself and known categories. #1034 is left as it is.Known limits
mount_pathis matched by folder names. If a category itself is named likecomplete_dir(a category calledcomplete), a release folder in it can be taken for a category folder, so these paths then leave its files alone. This errs on the safe side.Testing
deleteStarrQueuesendingRemoveFromClient=falsefor a category root, while still removing and blocklisting;removeFromClient=falsefor a category-folder output, and stilltruefor a release folder;Upgradewebhook listing the category folder,complete_dirand the mount root next to a real file (only the real file goes);tv/uhd) seen both insidemount_pathand from an arr that mounts it elsewhere.go vetandgo testpass forinternal/config,internal/arrs/scanner,internal/arrs/worker,internal/stremio,internal/apiandinternal/health. No frontend changes.The Radarr, webhook, stremio and logging parts have run in our fork since 2026-10-26. The Sonarr part is new, because #1033 changed that code.