SKYHOUSE.dev Journal

Maintaining the Cloud Fortress

Breaking the Queue Loop, and a Safety Assumption That Was Backwards

Why: Cleaning dead items out of the Sonarr/Radarr queues kept failing — they came back within minutes with the same IDs — because the arrs rebuild their queues from rdt-client, and rdt-client was holding 46 permanently-errored records it would never release.

This is the fix for the "I request media and it never arrives" complaint, applied at the layer where the problem actually lives. It also contains the sharpest self-inflicted mistake of this whole project, recorded in detail because a future session reading the original reasoning would repeat it exactly.

1. The loop

rdt-client asks TorBox to add a release; TorBox rejects it as a duplicate (DIFF_ISSUE: Download already queued); rdt-client keeps the failed record forever. The arrs rebuild their queues from the download client, so they keep re-displaying a download that can never finish. Removing it from the arr does nothing — the next sync restores it.

Worse, the first version of the cleanup tool refused to touch those records because an arr still referenced them. That gate exists to protect in-flight downloads, but an errored record is by definition not in flight, and the arr only references it because rdt-client holds it. The two layers were protecting each other and the dead record survived indefinitely. Breaking that required an explicit opt-in flag (--include-arr-referenced), kept opt-in precisely because it inverts a safety gate.

2. The mistake: "all flags false must be safest"

rdt-client's delete endpoint is POST Api/Torrents/Delete/{id} with a JSON body of three flags:

{deleteData, deleteRdTorrent, deleteLocalFiles}

The tool was written setting all three to false, reasoning that false is the conservative choice, with a confident docstring explaining that this made it structurally incapable of touching files. It ran, reported Deleted: 38, and deleted nothing at all.

The flags do not add extras on top of a base deletion — they specify what to delete. All-false is a no-op. And the endpoint returns HTTP 200 unconditionally (verified: it returns 200 for a nonexistent GUID), so the response carried no information whatsoever. The run was not even entirely inert: it rewrote each record's error to "Torrent deleted" while leaving the record in place.

The correct configuration, read out of the SPA bundle rather than guessed:

deleteData       = True    ← rdt-client's own DB record. This is the one we want.
deleteRdTorrent  = False   ← would remove it from TorBox
deleteLocalFiles = False   ← would delete downloaded files on disk

Two lessons worth more than the fix. First: a safety argument that sounds airtight can be exactly inverted, and the confident docstring made it harder to question, not easier. Second: the failure was caught by re-counting records afterwards, not by anything the tool reported — and the decisive clue (a bogus GUID also returning 200) had been observed earlier and not acted on.

3. Verified before bulk use, on all three layers

Rather than trusting the corrected reasoning, the new flags were proven on a single zero-file errored record first, measuring each layer before and after:

rdt-client records   51 -> 50        record removed
staging entries     236 -> 236       no files touched
TorBox entry     absent -> absent    provider untouched
/media/plex1/Movies                  byte-identical listing

Only then was it run in bulk. The tool now also re-reads the torrent list after applying and reports what actually disappeared, because HTTP 200 from this endpoint is meaningless.

4. Result

VERIFY TV     : 51 -> 15 records,  2 still errored (all under the 24h age gate)
VERIFY MOVIES : 44 -> 42 records,  6 still errored (all under the 24h age gate)
Deleted: 37; kept: 57

movies dirs : 1599 -> 1599  identical (6.1 TB)
staging     :  236 ->  236  unchanged
torbox      :   15 ->   15  unchanged

The arrs now have nothing dead left to re-serve.

5. Standing safety properties of ~/bin/rdtclient-cleanup.py

Thirty-seven dead records cleared, the arr/rdt-client loop broken, and a safety assumption caught being precisely backwards before it could matter.

← Back to Admin Hub