Automation guide
Stewarrd can act on downloads in three ways, each configured as a policy:
- Recovery replaces failed and stalled downloads: it blocklists the release in Sonarr or Radarr, removes it from the client while keeping its files, and searches for a replacement.
- Copy-import recovery imports completed downloads that Sonarr or Radarr blocked only because it could not match them to an ID.
- Unsafe-content cleanup removes downloads that contain executables or other unsafe files, and rejects the release so it is not grabbed again.
Every policy starts in preview: it reads client and Arr state and records what it would do in its journal, but sends nothing. Enabling a policy authorizes only that policy's writes described below. Manual controls in the interface always act immediately.
Run only one automation tool against the same downloads. When replacing another tool, keep Stewarrd in preview while you compare its findings, then stop the old tool before enabling the matching Stewarrd policy. Before enabling a policy, test it on disposable downloads in an environment you control: preview cannot prove that a client will delete, import or recover a real download correctly.
Before you enable anything
Connections and mappings
Add each download client and each Sonarr or Radarr instance, then map every client to the exact download-client name it has in that Arr. Active policies lock a connection's identity and its existing mappings; you can add mappings between cycles.
Read-only mounts
Policies read evidence from directories you mount read-only into the container:
- Evidence databases under
/evidence: the Sonarr and Radarr configuration directories, and the RDT-Client database directory for RDT clients. Mount the directories, not single files, so the-waland-shmfiles are included, and keep them readable by UID 1000. Policies refer to them by relative path, such assonarr/sonarr.dborrdt/rdtclient.db. - Downloads under
/downloads, for copy-import recovery and cleanup.
volumes:
- stewarrd-data:/data
- /srv/downloads/complete:/downloads/complete:ro
- /srv/rdt/db:/evidence/rdt:ro
- /srv/sonarr/config:/evidence/sonarr:ro
- /srv/radarr/config:/evidence/radarr:ro
Never mount the Docker socket. Clients make their own changes through their APIs.
Sonarr and Radarr settings
- Turn off Remove Failed for every client that replacement recovery uses. Arr's failure handling can delete files even when Stewarrd asks it not to remove them from the client, so Stewarrd checks this setting before blocklisting. Stall recovery through an Arr client of type qBittorrent, which includes RDT-Client, is exempt: Sonarr and Radarr only remove such a download once it has finished, stopped and reached its seed limit, so a transfer still in progress is never removed.
- Turn off Remove Completed for clients that copy-import recovery uses.
- Leave Completed Download Handling on for normal imports. Arr handles failed SABnzbd downloads itself, so Stewarrd never creates failed-release plans for SABnzbd.
These settings are checked in preview as well as when a policy runs.
RDT-Client settings
- Turn off provider auto-import. When RDT-Client deletes a record, it deletes the record before the provider torrent and ignores a failed provider deletion. With auto-import on, a surviving torrent would be imported again and downloaded again.
- Set Delete download when in error to 0, so RDT-Client does not delete records, provider data and local files on its own while Stewarrd is handling them.
Health endpoint
Each recovery policy needs a health URL that returns success only when the download
network path is healthy, for example your VPN's health check, and for SABnzbd also its news
server. It must be reachable from the Stewarrd container: an endpoint bound only to another
container's loopback is not. Stewarrd's own /healthz reports Stewarrd's health, not your
VPN's, and must not be used here.
Recovery
Recovery creates a plan for each download that qualifies, then works through it over several cycles. There are three kinds of plan.
Failed RDT-Client releases
Only these RDT-Client errors mean the release itself failed and qualify for replacement:
Debrid error: No peer after 30 minutes.Debrid error: error., only when the record's native status iserrorand the client uses Real-Debrid. Only Real-Debrid's generic error status is known to mean a bad release.
The failure must be at least 15 minutes old, with no scheduled RDT-Client retry, no local
download rows and no unsafe filenames. Generic qBittorrent and RQBit errors usually mean
storage problems, not bad releases, so they never qualify. Could not add to provider: Infringing file does not qualify either: Real-Debrid also returns it while it pauses every
add on the account, so it is handled only by empty-record repair, which never blocklists.
Stalled downloads
A recovery policy can also replace downloads that have stopped making progress. This is a
separate switch: stall: { "minutes": 60, "enabled": false }, with a window from 15
minutes to 7 days. Replacement needs both the policy and its stall rule enabled.
A download qualifies after making no progress for the whole window. It must be an active transfer with a known size and progress. These never qualify:
- metadata acquisition, queued, checking, extracting and completed downloads
- private torrents, because removing one stops seeding and risks a tracker hit-and-run.
qBittorrent reports privacy from version 5.0, so older versions have no qualifying
torrents. RQBit's flag is read from each candidate's torrent file, and an unreadable file
excludes only that torrent. Excluded transfers are reported as
download stallednotices. - RDT-Client transfers paused by RDT-Client's storage check
- RDT-Client provider transfers unless the provider's own status starts with
downloadingorstalled. RDT-Client also reports provider queues, compression, pauses and checks as downloading. Premiumize'srunningis excluded because RDT-Client always reports zero speed and size for it. - SABnzbd waiting rows, which SABnzbd also reports as downloading. Only its first eligible row qualifies, and only while SABnzbd reports that its global and post-processing pauses are off.
The window restarts on progress, positive speed, a pause, a phase change, or a gap of more than five minutes between observations. Any failed read during discovery also restarts it, including RDT-Client failed-release discovery, which runs first. The one exception is a failed grab-history read for a single download after the observation was recorded: the windows are kept and that plan is tried again on the next cycle.
Before blocklisting, the download's name, status, category, size and progress must still match the observation. After blocklisting only the name and category must match, so a download that resumes cannot leave a rejected release in place.
Empty RDT-Client records
When Real-Debrid refuses an add, RDT-Client can be left with an empty record: no name, provider torrent, files or local downloads. Such a record can make Sonarr or Radarr's queue fail on its missing title. Deleting it loses nothing, so repair is less strict than replacement:
- A plan starts only while the Arr's logs show its null-title queue exception within the last five minutes, and removal checks this again. RDT-Client 2.0.135 and later report a refused add to Arr, which then records no grab, so repair does not depend on grab history.
- The RDT-Client database and API must agree, with no provider files or local downloads.
- Every enabled Arr client must be mapped. Among the nameless records in this Arr's category, the client's inventory must list this one. Records in another Arr's category never reach this Arr's queue and are ignored. Several failed nameless RDT-Client records are each repaired by their own plan; any other nameless record, such as a native qBittorrent torrent, blocks repair until you deal with it.
- Repair deletes the RDT-Client record only, never provider data or local files, so it needs no provider account, network health or auto-import check.
After the deletion, the Arr must complete two queue refreshes with a complete queue and no recent queue failure. A replacement search then needs a grab through the same client close to the record's creation. With no grab at all, or a latest grab through another named client, that client already has the media and the plan completes without searching. A latest grab through this client outside that window, or with no client name, is held for review.
What a recovery plan does
- Checks ownership and evidence. The Arr's queue and grab history must agree on the client, download, series or movie, and affected episodes. RDT-Client's database must agree with fresh native detail, its provider account must be live with a future expiry, and the health endpoint must succeed. Protection is checked before every write.
- Blocklists the release through the Arr with
removeFromClient=false,blocklist=trueandskipRedownload=true, then confirms a new matching blocklist row in the Arr's database before going further. Torrents match their hash; Usenet matches the grab's series or movie, title, quality, indexer, size and publication date. An older blocklist row never counts as proof. - Removes the download, keeping its files. qBittorrent removes with
deleteFiles=false, RQBit usesforget, and SABnzbd removes stalled queue records withdel_files=0. RDT-Client keeps local files (deleteLocalFiles=false). A fresh, complete client listing must confirm the download is gone. - Searches for a replacement for missing, monitored media only, skipping media that is already queued or in the library, and respecting Sonarr's series monitoring. The plan finishes when the library actually has the files, or the media is unmonitored, not when the search command completes.
RDT-Client provider torrents. A failed release, or a stall while the provider is still
downloading, also deletes the provider torrent (deleteRdTorrent=true): it has nothing
worth keeping and otherwise holds one of the account's active download slots. The provider
torrent is kept when a local download has started, because the local files still need it.
It is also kept when any other RDT-Client record references it, including the same record
in another RDT-Client's copied database, or when any RDT-Client connection has no recovery
policy to check against.
Limits. At most three removals per cycle, three removals and three searches per media item per rolling day, and an hour between searches.
Uncertain writes. Every write intent is saved before the request is sent. If the outcome is unknown, Stewarrd checks the native state on later cycles and never resends the request blindly.
SABnzbd can resume a paused duplicate when a queue record is removed. Stewarrd checks the Arr's queue before searching, but that duplicate is SABnzbd's behaviour, not a Stewarrd search.
Holds and resuming
A plan that cannot safely continue is held, and its journal says why. Fix the cause and the
plan continues on the next cycle. A tracking plan held for review can be resumed with the
recovery journal's Review hold form, which calls
POST /api/recovery/plans/:id/resume with checkedAt and a review note. Resuming reads
current Arr state and sends no external writes.
- Only tracking plans can resume: an uncertain blocklist or delete cannot be acknowledged into success.
- A running or recent search blocks resuming. For an old search whose command is no longer available, you acknowledge the uncertainty after checking the Arr yourself.
- The original intent, timestamps and limits remain; the acknowledgement and note are added to the journal. Resuming starts a new progress window and allows further searches within the policy's limits. It does not prove the old search finished or never ran.
- Stop tracking keeps the journal and does not undo anything already done.
Copy-import recovery
Sonarr and Radarr sometimes block a finished download only because they cannot match it to an ID. Copy-import recovery imports such downloads with Arr's manual-import command.
Configure an Arr instance and a storage mapping in Imports. For example, mount
/srv/downloads/complete:/downloads/complete:ro, set the remote root to the path the Arr
uses for completed downloads, and set the local root to complete.
Only the Arr's ID-only match warning qualifies. Permission, quality, language, unknown and other import errors are left for you to review. Before importing, Stewarrd requires:
- queue and grab history that agree on ownership, and a verified, completed local download
- an unprotected download whose release has never been rejected as unsafe
- regular, non-empty files with no symlinks or unsafe extensions, approved by the Arr's own import preview
- for Radarr, one file whose title and year independently match the movie
- for Sonarr, independently parsed series and episodes that match every preview and exactly the episodes in grab history
Imported or unmonitored media is never overwritten, and imports always copy, so the source files stay in place. Stewarrd checks again that the media is missing and monitored immediately before submitting. The submission is recorded first, and an unknown outcome is checked rather than resubmitted. The plan completes only when the Arr reports a library file for every submitted movie or episode.
Import commands omit downloadId, so the copy is not tied to the Arr's tracked completion
cleanup. As a result, the Arr can leave the original blocked queue entry visible after the
library file is confirmed. The manual-import command has no "only if missing" option, so
another importer writing at the same moment can still race it. Do not run competing import
tools or change source-retention settings while an import is pending.
Unsafe-content cleanup
Cleanup removes downloads that contain executables or scripts, then rejects the exact release title in the Arr applications that owned it, so it is not grabbed again. Enabling cleanup permits permanent deletion.
Map your storage
Mount every connected client's download directories read-only under /downloads; RDT-Client
also needs its database under /evidence. If a client stores incomplete downloads
separately, map that storage too with incompleteRemoteRoot and incompleteLocalRoot, for
example /incomplete and incomplete/sabnzbd. Every connected client must be mapped before
Stewarrd can prove a payload is not shared with another download. Unmapped folders block
cleanup, and different container paths do not prove separate physical storage.
What it detects
Unsafe files are found from the Arr's own unsafe-content warnings and from the clients' file
lists: qBittorrent and RQBit selected files, RDT-Client's downloaded filenames and actual
local files, and SABnzbd history payloads through the read-only mount. RDT-Client's provider
Selected flags have different meanings per provider and are not used. Deselected RQBit
files do not make a download unsafe by themselves, but all of its files take part in the
sharing and link checks, because RQBit's deletion can remove previously downloaded files.
Each detection raises one Unsafe content found notice, whether cleanup is enabled, in
preview, or the download turns out not to be grabbed by an Arr. Cleanup acts only on
downloads an Arr grabbed: jobs for downloads added by hand are cancelled without deletion.
One unreadable download is logged and raises a notice without stopping inspection of the
client's other downloads. History is read from a cursor that only advances after a
successful read. The journal shows every inspected path.
What blocks removal
Shared roots, path traversal, symlinks, hardlinks, payloads shared with another download, protected downloads, uncertain manual actions and incomplete inventories all block removal.
Per client
- qBittorrent: the full file list must match the reported content and its completed and incomplete paths. Both locations take part in the storage and sharing checks. Moving storage, root targets, path disagreement and linked files block deletion.
- RDT-Client: cleanup deletes the record, its local files and its provider torrent. It requires provider auto-import off, checked before the deletion is recorded, so turning auto-import off lets a held job continue. A provider torrent shared with another job blocks deletion.
- SABnzbd: a completed download needs a tracked Arr queue entry whose output path exactly
matches SABnzbd's completed storage. That path must lie strictly inside SABnzbd's
completed folder and must not be, or contain, the completed folder or a category folder,
so a release named like its category is refused. Stewarrd sends one Arr queue removal
with
removeFromClient=true&blocklist=false&skipRedownload=true; the Arr deletes the data and the SABnzbd history entry, and Stewarrd confirms both are gone. A failed download can use SABnzbd's own history deletion only when every reported path lies inside that job's incomplete directory. Queue file lists can reveal unsafe content but give no provable per-job directory, so queued jobs wait for history, and they hold the whole incomplete root, which can block cleanup of another SABnzbd download there. Completed history with no matching Arr queue entry needs manual review. - RQBit: cleanup deletes by torrent hash, never by RQBit's reusable numeric ID.
After removal
Every deletion is recorded before it is sent. A missing client record alone does not prove
success: the recorded paths must also be empty or gone. An unknown outcome is checked on
later cycles and never resent automatically. Release rejection is confirmed in each owning
Arr, and an uncertain rejection write needs readback or your review before it is retried.
A completed cleanup raises a Unsafe download removed notice and records the release, which
then blocks imports and recovery of it. If a client lists a removed download again with
unsafe files, cleanup reopens it and raises Unsafe download returned. The Arr's queue and
history alone never reopen a job, because they can lag behind a confirmed removal.
Notifications and delivery
Stewarrd tracks problem conditions and notifies once each has persisted long enough, then again when it ends. The installation guide lists every notification and what it means.
- Arr health warnings notify after five minutes, failed downloads after 15 and blocked imports after 30 minutes of continuous observation. A Sonarr episode waiting for its title within 48 hours of a valid air date is not reported. An outage keeps existing findings and restarts their timing when collection resumes.
- Failed qBittorrent, RDT-Client and RQBit downloads notify after 15 minutes, because Arr may report them only as warnings. RDT-Client's error is included only when it is one of the status messages RDT-Client writes itself; anything else could contain download links or credentials and is replaced by a generic message. Downloads an enabled recovery plan is handling, and failed SABnzbd downloads, which Arr handles, are excluded.
- Where stall recovery is enabled, transfers it skips notify as
download stalledafter the shortest stall window without progress.
Titles name what happened and to which download, and details name the client and Arr and
give the reason, with links removed from Arr's own messages. When a condition ends, the
event title starts with Cleared: if the problem resolved, or Monitoring stopped: if
Stewarrd stopped watching it: for example a recovery plan was discarded or took over the
download, a policy was turned off, or a client or Arr was removed.
Webhook delivery
Set STEWARRD_NOTIFICATION_URL to a JSON webhook endpoint. Each event contains id,
scope, key, event, title, detail and type. Events are attention, cleared,
withdrawn (monitoring stopped) and completed (a verified cleanup); types are warning,
info and success. The event ID is also sent as Idempotency-Key; deduplicate on it,
because a timeout can follow a successful delivery. Signed URLs and native exception text
are never included. This is a generic webhook, not a Slack-specific payload.
Activity shows the outbox and any delivery failures. Network failures, HTTP 5xx, 408, 425, 429 and Apprise's 424 are retried with increasing delays. Any other 4xx stops delivery of that event until you correct the endpoint and choose Retry delivery. Events for one condition are delivered in order, so an undelivered or stopped event holds back later events for the same condition, which Activity shows as waiting. Discard drops a stopped event so the later ones can go out; other conditions are unaffected. Delivered events cannot be retried.
For Apprise's stored destinations, use http://apprise:8000/notify/YOUR_KEY?:detail=body,
which maps detail to the message body and passes title and type through. Both
containers must share a network. Keep the destination key private.
STEWARRD_NOTIFICATION_IGNORE_HEALTH takes comma-separated Arr health source names to leave
out of notifications, for example IndexerStatusCheck,IndexerLongTermStatusCheck. Those
findings stay visible in Media; download failures and blocked imports are unaffected.
Native controls and other client automation
RDT-Client Retry deletes data. It deletes the local files and the provider record, then
adds the original torrent or magnet again. The interface asks for confirmation, and API
callers must send confirmDataDeletion: true with the retry action. Retry requires cleanup
storage mappings, provider auto-import off and the original torrent or magnet. Protection,
unresolved activity, a shared payload or provider record, unsafe paths and linked files
block it. RDT-Client accepting the request does not prove the new provider download
succeeded, and because RDT-Client can hide provider-deletion errors, Stewarrd never claims a
confirmed provider deletion.
RQBit can pause and resume torrents that are still initializing as well as transfers, and retry restarts a failed torrent. It has no recheck.
SABnzbd retry uses SABnzbd's own reconstruction and can change the job ID; the next refresh reads the new inventory.
Clients keep their own automation. Stewarrd's file retention covers only what Stewarrd
asks for. qBittorrent's deleteFiles=false keeps payload files but removes its part file and
resume data, and its ratio or seeding limits can still remove completed payloads. Stewarrd
does not change those settings or protect files from actions the clients take themselves.
What has been tested
Recovery, copy-import and cleanup writes are tested against HTTP fixtures for all four clients, backed by real SQLite evidence databases, with temporary payloads, lost responses, ownership, storage and protection checks. Import tests also cover title and year mismatches, unsafe files, symlinks, rejected releases, multi-file and multi-episode downloads, unmonitored episodes and partial completion. Read-only checks against live clients cover all four client inventories, and read-only checks against Sonarr 5.0.0.2569 verified its queue, episode, series and filename-parsing behaviour.
Live provider deletion, filesystem deletion and library imports have not been validated against real downloads.