ADR-0012: SnatchArr manages the download queue; the Cleanuparr link goes¶
- Status: accepted
- Date: 2026-10-17
- Supersedes: ADR-0004 in full, ADR-0007 in full and ADR-0009 in full.
- Bounds: ADR-0006 stands. SnatchArr still never talks to a download client directly.
Context¶
ADR-0004 kept every download-queue action out of SnatchArr because Cleanuparr owned that domain. ADR-0007 then linked Cleanuparr read-only, and ADR-0009 read its Seeker settings to warn about double searching. As a result, an operator who wants stuck downloads cleared has to run, configure and secure a second application next to SnatchArr, and SnatchArr carries a client, a table, routes, a status page and a warning path whose only job is to cope with that second application.
The queue items worth acting on are visible in the *arr queue API
(GET /api/v3/queue, GET /api/v1/queue), which SnatchArr already reads to skip queued items:
trackedDownloadState:downloading,importBlocked,importPending,failedPending,failedand the others in the pinned specs.trackedDownloadStatus,statusandstatusMessages.sizeleftandcustomFormatScore.
The *arr queue API removes an item, removes it from the download client and blocklists the
release in one call: DELETE .../queue/{id} with removeFromClient, blocklist and
skipRedownload. Queue management therefore needs no download-client credentials and no
second application. It was requested in lusoris/SnatchArr#48, under epic
lusoris/SnatchArr#45.
Decision¶
- *SnatchArr manages the arr download queue. A periodic sweep classifies each queue item of each enabled instance into the rule classes stalled, slow, failed, import-blocked and unwanted. It removes an item only after the item breaks the same rule on a configured number of consecutive sweeps (strikes). The behaviour of each rule, its defaults and its planted test case are specified in lusoris/SnatchArr#362. That issue, not this record, is the reference for thresholds.
- *Actions go through the arr only. A removal is
DELETE .../queue/{id}withremoveFromClient=trueandskipRedownload=true.blocklist=trueis set for every class except unwanted-because-unmonitored, so the *arr never grabs the same release again. SnatchArr calls no download-client API, and ADR-0006 stands. - The replacement search is SnatchArr's own. With
skipRedownload=truethe *arr does not search by itself. The item drops back into the wanted list, and SnatchArr's planner searches it like any other wanted item: under the instance's hourly cap and subject to processed memory and afterglow. - The api decides and the sweep runs on the leader. The sweep is a
leader_loopsmember (leaderx.Provide). Strike state lives in Postgres with a per-rowexpires_at, and the api is its only writer, as it is for processed memory. Each sweep has a scalar bound on items read and on removals per instance (HISS-02, HISS-06). - Observe first. Queue management is configured per instance in the snatch policy. Every rule starts in observe mode. An observed strike is recorded and shown and emits an event, but the item is not removed. The operator switches a rule to act per instance.
- The Cleanuparr link is removed. This covers the client package, the
cleanuparr_linkstable (a down-migration drops it), the/api/v1/cleanuparrroutes and theseeker_overlapfield of theInstanceschema. It also covers the status page, the Seeker badge and banner, the configuration keys, the docs and the tests. The goenvoyarr/cleanuparrpackage stays in goenvoy, and SnatchArr stops importing it. - No outside code. The rules are written from the pinned *arr OpenAPI specs and the behaviour issue. No code is copied from Cleanuparr or from any private script.
Consequences¶
- Positive:
- One application covers snatching and queue hygiene.
- Operators drop a second service, its credentials and its configuration.
- Each replacement search counts against the same hourly cap as every other search, so queue cleanup cannot cause a burst of searches.
- The code that existed only to cope with Cleanuparr leaves (link, status page, Seeker detection).
- Negative:
- SnatchArr now carries a destructive action. Observe mode is the default, and a rule acts only after the strike count and only within the per-sweep removal bound. Each rule ships a planted case that must fail before the rule is trusted.
- The
/api/v1/cleanuparrroutes and theseeker_overlapfield leave the public API. Their removal is a breaking change (HISS-14): the commit subject carries!and aMigration:footer. - Operators who keep Cleanuparr for its other features (malware blocking, orphan cleanup) must not let both tools act on the same queue. The docs say so. SnatchArr does not detect this case any longer.
- Rollout: the Cleanuparr deployment leaves the reference stack only after queue management runs green there. That step is tracked in #48 and is not part of this record.