Skip to content

fix(library): a destination equal to a configured root plans the book folder instead of claiming the root - #915

Open
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:fix/root-destination-add
Open

fix(library): a destination equal to a configured root plans the book folder instead of claiming the root#915
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:fix/root-destination-add

Conversation

@kevinheneveld

Copy link
Copy Markdown
Contributor

The bug

LibraryAddService persists a provided DestinationPath verbatim as the audiobook's BasePath. When a client passes the root folder path itself ("put it under this root" — my series bulk-add UI does, and any API caller can), the record ends up with BasePath = <root>. Two failure modes:

  1. One such record poisons all future adds to that root. The post-Harden path identity, durable moves, scans, and root relocation #717 destination mutation guard rejects any destination "already assigned to another audiobook" — with a record claiming the root, every subsequent root-destined add fails destination_path_blocked. On my instance 102 wishlist records had accumulated BasePath = /audiobooks, at which point bulk-adding an entire series failed 100% (that's how I found it).
  2. Imports land loose in the library root. When such a record's download completes, files import into BasePath = the root — 10 of my records had scattered ~330 loose files at the top of the library before the guard started blocking.

The fix

Treat a destination that is a configured root as a root selection: plan the book folder under the chosen root with FolderNamingPattern, exactly as an omitted destination plans one under the default root. Destinations already inside a root are unchanged.

Regression test: add with DestinationPath = <configured root> asserts the persisted BasePath is the planned {Author} folder under the root, not the root.

🤖 Generated with Claude Code

… folder instead of claiming the root

Adding with DestinationPath set to a configured root folder persisted the
root itself as the audiobook's BasePath. That has two failure modes: the
destination mutation guard then rejects every later add aimed at that root
(destination_path_blocked — 'already assigned to another audiobook'), so
one such record poisons all root-destined adds; and when the record's
download eventually imports, its files land loose in the library root.

Treat a root-equal destination as a root selection: plan the book's folder
under the chosen root with the folder naming pattern, exactly as an
omitted destination plans it under the default root. Destinations already
inside a root are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kevinheneveld
kevinheneveld requested a review from a team August 31, 2026 18:38
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Aug 31, 2026
… folder (PR Listenarrs#915)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, not legacy host-path helpers

FileUtils.IsPathSameOrInside/IsPathInsideOf are on the architecture test's
forbidden list (LegacyHostPathIdentity_StaysOnExplicitAllowList); compare
canonical spellings via FileSystemPathIdentity instead — conservative for
the root-selection rewrite, since the selection arrives as the root's
stored path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant