[Bug]: Watcher selective scan leaves orphan folder rows when a new Artist/Album tree is created ("Parent folder not found for album cover art lookup")
I confirm that:
- I have searched the existing open AND closed issues to see if an issue already exists for the bug I've encountered
- I'm using the latest version (running 0.63.2; I read the v0.64.0
scanner/sources and could not find anything that would change this behavior, see below)
Version
0.63.2
Current Behavior
When a tool creates a new two-level directory (Artist/Album/track.mp3) inside a library while Navidrome is running, the folder watcher triggers a selective scan that inserts a folder row for Artist/Album, but no row for the newly created Artist folder. The Artist/Album row keeps a parent_id that points to a folder id that does not exist in the folder table.
These orphan rows are not repaired by the scheduled (non-full) scans. Every cover art request for such an album then logs:
level=warning msg="Artwork: Parent folder not found for album cover art lookup" parentID=4CuRL8UDm337MXN9PoCXsz(albumRootParent() in core/artwork/folders_album.go gets ErrNotFound for the parent.)
In my case the tool is Explo, which downloads playlist tracks into a dedicated library as Artist/Album/Title.mp3. After one weekly run, 22 album folders (under 21 new artist folders) were orphaned. All 22 orphan rows have a created_at inside the window of the three watcher-triggered selective scans that ran during the download. Album folders created on earlier dates in the same library have no orphaned parents.
State of the DB after the run (library 3 is the Explo library):
SELECT f.library_id, f.missing, count(*)
FROM folder f
WHERE f.parent_id <> ''
AND NOT EXISTS (SELECT 1 FROM folder p WHERE p.id = f.parent_id)
GROUP BY 1, 2;
-- 3 | 0 | 22Example: the folder Ado/残夢 exists with parent_id = 4CuRL8UDm337MXN9PoCXsz, the directory Ado/ exists on disk, but SELECT count(*) FROM folder WHERE id = '4CuRL8UDm337MXN9PoCXsz' returns 0.
Expected Behavior
A selective (watcher-triggered) scan should make sure that all ancestor folders of the scanned targets exist in the folder table (or should scan the nearest new ancestor instead), so that folder.parent_id always points to an existing row.
Steps To Reproduce
- Run Navidrome with the folder watcher enabled (default) and a library at
/music/lib. - While Navidrome is running, create a new artist and album in a single step, e.g.
mkdir -p "/music/lib/NewArtist/NewAlbum" && cp track.mp3 "/music/lib/NewArtist/NewAlbum/". - Wait for
Watcher: Triggering scan for changed folders/Watcher: Scan completed. - Query:
SELECT id, parent_id FROM folder WHERE name = 'NewAlbum';then check thatparent_idexists infolder. - Request the album cover art: the warning above is logged.
Workaround / confirmation: a full selective scan of the library root repairs it. navidrome scan --full -t 3:. created the 21 missing artist rows in 3.8s (library 3 folder count 439 -> 460, orphan count 22 -> 0, track count unchanged).
Environment
- OS: Debian 13 (trixie), arm64 (Raspberry Pi 5)
- Browser: n/a (clients: Web UI and a Subsonic iOS client)
- Client: n/a
How Navidrome is installed?
Docker
Configuration
ND_SCANNER_SCHEDULE="0 5 * * *"
# Three libraries; the affected one is a local Docker volume (not a network/FUSE mount).
# Watcher settings are defaults.Relevant log output
level=info msg="Watcher: Triggering scan for changed folders" numTargets=10
level=info msg="Scanner: Starting selective scan" fullScan=false numLibraries=1 numTargets=10
level=info msg="Scanner: Completed processing folder" audioCount=1 library=explo tracksImported=1 folder="Ado/桜日和とタイムマシン - 初夏"
...
level=info msg="Watcher: Scan completed"
...
level=warning msg="Artwork: Parent folder not found for album cover art lookup" parentID=4CuRL8UDm337MXN9PoCXszAnything else?
- Possibly related: #5823 shows the same warning, although the cause described there is different.
- My reading of v0.64.0 (unverified):
resolveFolderPath()inscanner/watcher.goturns the file event into theArtist/Albumdirectory. When theArtistdirectory is created, the change shows up on the library root (the log showsfolder=.processed with its own audio count only).walkDirTree()inscanner/walk_dir_tree.gowalks each target recursively, but I could not find any code that inserts missing ancestor folders of a target. SoArtist/never becomes a scanned folder.
Code of Conduct
- I agree to follow Navidrome's Code of Conduct
Source: navidrome/navidrome