From 49e47a32bff03f28fb26d51950b695ea25e48a34 Mon Sep 17 00:00:00 2001 From: poprhythm Date: Tue, 8 Sep 2026 10:13:07 +0000 Subject: [PATCH] Document data-loss incident, pause qbt-relink.sh Babylon 5 S03/S04 (44 episodes) were destroyed during a relink attempt despite every safety check the script performs (HTTP status, save_path, content_path) passing. qBittorrent's own automatic incomplete-file management took further unrequested action after the script's calls succeeded - moving/truncating files at a location it still considered incomplete - which none of the script's checks could see coming since they only verify immediately after its own calls. No backup existed. Files could not be found anywhere on /data after a full search. Marking qbt-relink.sh unsafe until the interaction with qBittorrent's temp_path_enabled behavior is understood well enough to prevent this. Claude-Session: https://claude.ai/code/session_01HZQK6jHmdTpFjFZM8FUnqA --- .claude/skills/sonarr/SKILL.md | 61 +++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/.claude/skills/sonarr/SKILL.md b/.claude/skills/sonarr/SKILL.md index a312286..ff59e91 100644 --- a/.claude/skills/sonarr/SKILL.md +++ b/.claude/skills/sonarr/SKILL.md @@ -5,6 +5,20 @@ description: Use Sonarr to add TV shows and manually import existing raw-named f # Sonarr library migration/import +**⚠️ `qbt-relink.sh` is currently UNSAFE — do not use it until this warning is +removed.** It caused real, apparently unrecoverable data loss (Babylon 5 +seasons 3 and 4, 44 episodes) on 2026-09-08: qBittorrent's own automatic +incomplete-file management fought the script's manual `setLocation` calls +across repeated resume/stop cycles and physically moved/destroyed the real +files, even after every safety check the script performs (stopped state, +confirmed `save_path`, confirmed `content_path`) reported success. See +"Incident 3" below for the full detail. **The Sonarr import workflow itself +(`sonarr.sh scan`/`import`) is unaffected and still safe** — it's specifically +the qBittorrent-side relink step that's paused. Until this is fixed, leave +qBittorrent's stale torrent entries alone after a Sonarr import rather than +touching them — a `missingFiles` error in the qBittorrent UI is a much +smaller problem than what happened here. + This repo's Sonarr manages TV show organization for both Jellyfin and Plex (they share the same `nas_media` library at `/data/video/tv`). The `claude-homelab` plugin's Sonarr skill (if installed) only covers @@ -130,7 +144,52 @@ or its `state` is `checkingDL` instead of `checkingUP`**, check `content_path` vs `save_path` directly — a mismatch means the recheck is running against the wrong location and will "succeed" at finding nothing. -### Incident: an unchecked `setLocation` call let a torrent start re-downloading +### Incident 3: qBittorrent's own automatic file management destroyed real data despite every check passing — data loss, `qbt-relink.sh` is now paused + +2026-09-08, same overall session, later batch. Even with incidents 1 and 2's +fixes in place (`api_call` status checking, torrent stopped first, +`save_path` *and* `content_path` both confirmed correct before proceeding), +Babylon 5 S03 and S04 lost their actual files. The qBittorrent log +(`/api/v2/log/main`) tells the story: after `setLocation` moved them to +`Season 03`/`Season 04` (logged success), qBittorrent's *own* internal logic +immediately enqueued and executed a further move of those same files **back** +to `/data/torrents/incomplete/video/tv` on its own — nothing in the script +requested this. A later resume cycle moved them back to `Season 03`/`04` +again, but then repeated automatic resume/stop cycles followed (visible as +alternating `"Torrent resumed"`/`"Torrent stopped"` log lines with no +corresponding script action), and after those, both `Season 03` and +`Season 04` folders were **completely empty** — confirmed via `ls`/`stat` +from two separate containers, and a full search of `/data` found the files +nowhere. 44 episodes, gone. No backup existed (the user had explicitly +decided to proceed without one earlier in this project). + +Best-guess mechanism: qBittorrent's `temp_path_enabled` incomplete-file +management re-evaluates on every resume, independent of manual +`setLocation`/`setDownloadPath` calls, and can decide to relocate — or, +worse, truncate/overwrite in place expecting fresh downloaded data — files +at a location it still considers "incomplete" (i.e. never successfully +verified as 100% via a *clean* recheck). Since these torrents' recheck kept +getting interrupted/retriggered across multiple attempts (network/NFS +slowness caused several manual re-triggers in this session), the torrent +seems to have never reached a stable "verified complete" state internally, +leaving it perpetually eligible for this automatic (and here, destructive) +relocation — regardless of what the script's own state checks reported. + +**This means the safety checks this script relies on (`save_path`, +`content_path`, HTTP status) are necessary but not sufficient** — they +confirm the *script's* requests succeeded, but qBittorrent can still take +further unrequested action afterward that undoes or destroys the result, +and none of that is visible to a script that only checks immediately after +its own calls. `qbt-relink.sh` is paused (see the warning at the top of this +file) until this is understood well enough to prevent it — likely candidates +for a real fix: disabling `temp_path_enabled` globally before relinking +(and confirming no other torrent depends on it) as a global preference, an +approach that fully separates the qBittorrent-tracked download from the +Sonarr-organized library so they never share a path, or verifying via a +polling loop that a recheck reaches a genuinely stable end state (not just +"currently reports 100% right now") before considering a torrent safe. + +### Incident 1: an unchecked `setLocation` call let a torrent start re-downloading The first version of `qbt-relink.sh` fired `setLocation`/`renameFile`/ `recheck` with `curl -s ... > /dev/null`, discarding the HTTP status. Running