Skip to content

fix(arrs): skip queue items from other download clients#525

Merged
javi11 merged 7 commits intomainfrom
claude/mystifying-morse-fa18e1
Apr 22, 2026
Merged

fix(arrs): skip queue items from other download clients#525
javi11 merged 7 commits intomainfrom
claude/mystifying-morse-fa18e1

Conversation

@javi11
Copy link
Copy Markdown
Owner

@javi11 javi11 commented Apr 22, 2026

Summary

Fixes #523 — the ARR queue cleanup worker was removing queue items belonging to other download clients (qBittorrent, real SABnzbd, NZBGet…), not just AltMount's own.

Root cause: cleanupRadarrQueue / cleanupSonarrQueue in internal/arrs/worker/worker.go iterated every record returned by the ARR queue API. The isGhostByPathGone check then os.Stat'd the OutputPath; for items owned by other clients the path often points to a volume AltMount cannot see, so Stat fails, the 60s observation window elapses, and the item is deleted from the ARR queue with removeFromClient=true. Symptom in logs:

Found ghost queue item (source path gone after observation window), cleaning up

Fix: Skip any queue record whose DownloadClient field does not match AltMount's registered client name. The cleanup worker exists to clean up AltMount's own imports — touching other clients' items is never intentional.

Changes

  • internal/arrs/registrar/manager.go — export AltmountDownloadClientName = "AltMount (SABnzbd)" as a package constant and use it from EnsureDownloadClientRegistration, so both registrar and worker share one source of truth.
  • internal/arrs/worker/worker.go — import registrar; at the top of the queue loop in both cleanupRadarrQueue and cleanupSonarrQueue, continue when q.DownloadClient != registrar.AltmountDownloadClientName.

The webhook handler (internal/api/arrs_handlers.go) was not changed — it only reacts to import/rename/delete events for the single file in the payload and cannot mass-remove other clients' items.

Test plan

  • go build ./internal/arrs/...
  • go vet ./internal/arrs/...
  • go test -race ./internal/arrs/... — all green
  • Manual: with Radarr configured with both AltMount and a second download client, force a stuck/failed download on the other client and confirm it is not removed by AltMount's cleanup worker (no Found ghost queue item log for that item).
  • Manual regression: confirm legitimate AltMount ghosts (file already imported + source removed) are still cleaned up.

javi11 added 7 commits April 20, 2026 10:25
Adds a generic import_migrations table to track two-phase migration
state (Phase 1 = import NZBs into AltMount; Phase 2 = rewrite arr
library symlinks). Includes goose migrations for SQLite and PostgreSQL,
ImportMigration model + status consts, a full ImportMigrationRepository
with Upsert/MarkImported/MarkFailed/MarkSymlinksMigrated/LookupByExternalID/
ListByStatus/Stats/ExistsForSource/BackfillFromImportQueue, and wires
MigrationRepo into the DB struct.
…avIds

Delete HandleIDMetadataLinks and id_linker.go; remove UpdateIDSymlink and
RemoveIDSymlink methods from MetadataService; remove the .id sidecar write
block from WriteFileMetadata (read path preserved for Phase 2 compatibility);
remove FilterExistingNzbdavIds from QueueRepository, Repository, and the
BatchQueueAdder interface; remove related call sites in nzbfilesystem MOVE
handler and nzbdav scanner processBatch.
- Add MigrationRecorder interface to scanner package with UpsertMigration
  and IsMigrationCompleted methods
- Update NzbDavImporter to accept MigrationRecorder as second constructor
  parameter alongside BatchQueueAdder
- processBatch now checks IsMigrationCompleted before enqueueing (skip
  already-imported/symlinks_migrated items) and calls UpsertMigration for
  new items, then strips nzbdav_id from the queue item metadata leaving
  only extracted_files if present
- createNzbFileAndPrepareItem sets SkipArrNotification=true on all items
- batchQueueAdapterForImporter gains migrationRepo field and implements
  MigrationRecorder via ImportMigrationRepository.Upsert/LookupByExternalID
…lure

Wire s.database.MigrationRepo into handleProcessingSuccess and
handleProcessingFailure so that import_migrations rows are marked
imported/failed when the corresponding queue item finishes. Both
calls are non-fatal: failures are logged as warnings and do not
affect the main processing outcome. If no matching migration row
exists (non-nzbdav import), the UPDATE simply affects 0 rows.
Adds RewriteLibrarySymlinks in internal/importer/migration to walk a
library directory and atomically rewrite arr symlinks that point at
<nzbdav_mount>/.ids/<guid> to the final altmount path.  Introduces
DBSymlinkLookup adapter in internal/database, a new
POST /import/nzbdav/migrate-symlinks handler, and enriches the existing
status endpoint with migration_stats when available.
The ARR queue cleanup worker iterated every queue record and, via the
path-gone ghost check, removed items whose OutputPath was invisible to
AltMount — which includes items owned by other download clients
(qBittorrent, real SABnzbd, etc.). Filter the cleanup loops by
DownloadClient so only AltMount's own items are ever touched.

Fixes #523
…se-fa18e1

# Conflicts:
#	internal/api/nzbdav_handlers.go
#	internal/api/server.go
#	internal/database/import_migration_repository.go
#	internal/database/symlink_lookup.go
#	internal/importer/migration/symlinks.go
#	internal/importer/migration/symlinks_test.go
#	internal/importer/scanner/nzbdav.go
#	internal/importer/service.go
@javi11 javi11 merged commit 27869a6 into main Apr 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Arr Webhooks removes failed downloads of other Download Clients

1 participant