Skip to content

Retry automatic search without the author when the full query returns nothing (Fixes #1068) - #1072

Merged
therobbiedavis merged 3 commits into
Listenarrs:canaryfrom
dny238:fix/search-author-fallback
Oct 25, 2026
Merged

therobbiedavis merged 3 commits into
Listenarrs:canaryfrom
dny238:fix/search-author-fallback

Conversation

@dny238

@dny238 dny238 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #1068

Problem

Automatic search for a book could return 0 results even when the book is available, because the auto-built query is Title Author (Series) and many release names don't carry the author.

I verified this read-only against NZBgeek's API: …Full Cast Edition returned 5 results, but appending J.K. Rowling dropped it to 0 (release names omit the author); and over-specific multi-word queries AND-matched to nothing. (De-hyphenating made no difference — that's a separate normalization, see #1070.)

Fix

AutomaticSearchService now retries once with an author-free query (title + series) when the full query returns no results.

Safety: the automatic path scores results by quality only — relevance comes entirely from the query — so broadening the query could otherwise let a same-title/different-book release pass scoring and be grabbed. The fallback therefore constrains its results to titles that still match the book (ConstrainFallbackResultsToTitle, built on TitleUtils.IsMatchingTitle) before they reach scoring. Distinctive multi-word/long titles recover their releases; short or generic titles (where same-title collisions are likely) fail the match and are safely dropped, so nothing unrelated is grabbed.

Testing

AutomaticSearchResultClassifierTests:

  • the author-free query drops the author but keeps title + series;
  • the title constraint keeps a matching release (Andy Weir - Artemis - 2017 - M4B for Artemis) and drops an unrelated one (… The Martian …);
  • empty input returns empty.

The retry orchestration itself lives in the hosted-service loop (ProcessAudiobookAsync), which resolves ~7 services + a SignalR hub from a scope; happy to add a scope-driven integration test if you'd like it, but I kept the safety-critical pieces (query shape + title constraint) in the testable classifier.

🤖 Generated with Claude Code

@dny238
dny238 requested a review from a team October 20, 2026 23:33

@therobbiedavis therobbiedavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The author-free retry is useful, but the title guard still lets a different book reach automatic download selection. I reproduced this in a live container pass. This needs an identity check before the broadened results reach quality scoring.

@dny238

dny238 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #825, which already tracks the title-only retry — thanks for the pointer. I agree the retry is a band-aid over the real selection issue (ranking by quality before establishing the release matches the requested book), and #1054 is reworking the search path in that direction. The one piece worth carrying forward is constraining results to titles that match the requested book before quality ranking (the ConstrainFallbackResultsToTitle guard here); happy to fold that relevance idea into #825/#1054 if it's useful. Apologies for the duplicate.

@dny238 dny238 closed this Oct 21, 2026
@dny238 dny238 reopened this Oct 21, 2026
@Yoldark34

Yoldark34 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Backend matching is delegated to #1076

Any match revision before the step of doing a backend matching is welcomed IMO. We should revise matching because I will not change the buisness logic rules during the backend refactoring.

This PR should stay open then.

dny238 added a commit to dny238/Listenarr that referenced this pull request Oct 21, 2026
Addresses the Listenarrs#1072 review: the previous IsMatchingTitle (containment) guard
let a different book reach automatic download selection — e.g. Eoin Colfer's
"Artemis Fowl" for Andy Weir's "Artemis", and a different author's "Revival".

Replaces it with CanAutomaticallySelectFallback, following the maintainer's
conservative candidate: parse only "Title" or "Author - Title" layouts (with
optional trailing year/format suffixes), require the complete remaining title
to match the book exactly after normalization, and require at least one
corroborating signal (parsed author, Artist field, or ASIN) with any
conflicting evidence vetoing. The Artist field is only corroboration when it
matches (it can be a provider guess). Uncertain layouts and uncorroborated
authorless results are skipped, so this won't recover every title-only
release but refuses to grab a different book.

Tests cover a valid author-matched fallback, both reported collisions
(same-word title and same-title/different-author), an uncorroborated
authorless skip, and Artist-field corroboration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dny238

dny238 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Reopened — sorry, I'd closed this before your review landed. Thanks for catching the collision and for the detailed candidate; that's a real wrong-book hazard and the containment guard clearly wasn't enough.

Replaced IsMatchingTitle with CanAutomaticallySelectFallback following your direction: it parses only Title and Author - Title layouts (stripping trailing year/format suffixes), requires the complete remaining title to match the book exactly after normalization, and requires at least one corroborating signal — parsed author, Artist, or ASIN — with conflicting evidence vetoing. Artist only corroborates when it matches (never treated as confirmation just for being populated), and uncorroborated authorless results are skipped.

Tests (AutomaticSearchResultClassifierTests): a valid author-matched fallback is kept; both of your collisions are rejected — Eoin Colfer - Artemis Fowl for Artemis (title mismatch) and Chris Weitz - Revival for Stephen King's Revival (conflicting author); an uncorroborated authorless Artemis is skipped; and an Artist-field match corroborates.

On the full-cycle coverage: the guard is what gates fallback results before they reach selection, so these assert the collisions can't be selected. I kept them at the classifier level — the retry orchestration lives in the hosted-service loop (resolves ~7 services + a SignalR hub from a scope), which your own candidate's tests also stopped short of. Happy to add a scope-driven handoff test if you'd like that layer too.

@dny238

dny238 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Added the full-cycle regression coverage you asked for. The previous tests stopped at the classifier (ConstrainFallbackResultsToTitle); the new AutomaticSearchProcessorFallbackTests drive the whole automatic-search cycle via AutomaticSearchProcessor.RunCycleAsync and assert at the download-service handoff:

  • Same-word different book — Eoin Colfer's Artemis Fowl for Andy Weir's Artemis → StartDownloadAsync is never called (strict IDownloadService mock).
  • Same-title different author — Chris Weitz's Revival for Stephen King's → StartDownloadAsync is never called.
  • Correctly attributed fallback — Andy Weir - Artemis → StartDownloadAsync is called once with the requested book's id.

Both collisions are the ones you reproduced reaching StartDownloadAsync in the live container pass; these now lock that down at the cycle level. The valid case mocks the quality scorer (its own tests cover scoring) and uses a DDL result so the client selector resolves without configuring a download client. Pushed as 1a98902.

@therobbiedavis therobbiedavis left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dny238 The updated guard addresses the wrong-book cases I reproduced. I have one non-blocking follow-up suggestion for numeric titles: the missed recovery below also occurs before this PR, so I wouldn't hold this change up for it.

var parts = Regex.Split(result.Title.Trim(), @"\s+[-–—]\s+").ToList();

// Drop trailing year / format suffix segments ("... - 2017", "... - [M4B]").
while (parts.Count > 1 && Regex.IsMatch(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good follow-up PR candidate: could we preserve numeric book titles before treating trailing numbers as publication years? For George Orwell - 1984 - M4B, this loop removes both M4B and 1984, leaving George Orwell as the parsed title and dropping the correct release. David McCullough - 1776 - 2005 - M4B behaves the same way. This edge case was also in the prototype I suggested.

I compared the pre-PR path and this head in a live HTTP indexer container. When the full query returns nothing, neither selects these numeric-title releases; when it returns a valid 1984 result, both select it. The suffix parsing is new, but the missed recovery is not a user-visible regression—the previous path never retried.

For a follow-up, stop removing a segment when it matches the requested title:

while (parts.Count > 1
    && !string.Equals(
        NormalizeIdentity(parts[^1]),
        NormalizeIdentity(audiobook.Title),
        StringComparison.Ordinal)
    && Regex.IsMatch(
        parts[^1],
        @"^(?:\d{4}|\[?(?:m4b|mp3|flac|aac|ogg|opus)\]?)$",
        RegexOptions.IgnoreCase))
{
    parts.RemoveAt(parts.Count - 1);
}

That candidate recovered both numeric titles in the same container while still rejecting conflicting-author and uncorroborated authorless results. Add cases for a numeric title alone, with a format suffix, and with a separate publication year, retaining those negative controls. Validation stops at the recorded StartDownloadAsync handoff; no actual media download or import was exercised.

therobbiedavis pushed a commit to dny238/Listenarr that referenced this pull request Oct 22, 2026
Addresses the Listenarrs#1072 review: the previous IsMatchingTitle (containment) guard
let a different book reach automatic download selection — e.g. Eoin Colfer's
"Artemis Fowl" for Andy Weir's "Artemis", and a different author's "Revival".

Replaces it with CanAutomaticallySelectFallback, following the maintainer's
conservative candidate: parse only "Title" or "Author - Title" layouts (with
optional trailing year/format suffixes), require the complete remaining title
to match the book exactly after normalization, and require at least one
corroborating signal (parsed author, Artist field, or ASIN) with any
conflicting evidence vetoing. The Artist field is only corroboration when it
matches (it can be a provider guess). Uncertain layouts and uncorroborated
authorless results are skipped, so this won't recover every title-only
release but refuses to grab a different book.

Tests cover a valid author-matched fallback, both reported collisions
(same-word title and same-title/different-author), an uncorroborated
authorless skip, and Artist-field corroboration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@therobbiedavis
therobbiedavis force-pushed the fix/search-author-fallback branch from 1a98902 to 9f69405 Compare October 22, 2026 13:34
@therobbiedavis therobbiedavis added the patch patch version bump - backward compatible bug fixes label Oct 22, 2026
therobbiedavis pushed a commit to dny238/Listenarr that referenced this pull request Oct 25, 2026
Addresses the Listenarrs#1072 review: the previous IsMatchingTitle (containment) guard
let a different book reach automatic download selection — e.g. Eoin Colfer's
"Artemis Fowl" for Andy Weir's "Artemis", and a different author's "Revival".

Replaces it with CanAutomaticallySelectFallback, following the maintainer's
conservative candidate: parse only "Title" or "Author - Title" layouts (with
optional trailing year/format suffixes), require the complete remaining title
to match the book exactly after normalization, and require at least one
corroborating signal (parsed author, Artist field, or ASIN) with any
conflicting evidence vetoing. The Artist field is only corroboration when it
matches (it can be a provider guess). Uncertain layouts and uncorroborated
authorless results are skipped, so this won't recover every title-only
release but refuses to grab a different book.

Tests cover a valid author-matched fallback, both reported collisions
(same-word title and same-title/different-author), an uncorroborated
authorless skip, and Artist-field corroboration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@therobbiedavis
therobbiedavis force-pushed the fix/search-author-fallback branch from 9f69405 to 9d27813 Compare October 25, 2026 03:25
dny238 and others added 3 commits October 25, 2026 00:26
… nothing

Fixes Listenarrs#1068: automatic search for a book returned 0 results when the release
names don't carry the author. A read-only comparison against NZBgeek showed
the title+author query returning nothing while the title-only query returned
the releases (de-hyphenating made no difference; the author and query
specificity were the cause).

AutomaticSearchService now retries once with an author-free query (title +
series) when the full query returns no results. Because the automatic path
scores by quality only — relevance comes from the query itself — the broadened
fallback results are constrained to titles that still match the book
(ConstrainFallbackResultsToTitle, via TitleUtils) before they reach scoring,
so a same-title/different-book release can't be grabbed. Short/generic titles
fail that match and are safely dropped.

Adds AutomaticSearchResultClassifierTests for the author-free query and the
title constraint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses the Listenarrs#1072 review: the previous IsMatchingTitle (containment) guard
let a different book reach automatic download selection — e.g. Eoin Colfer's
"Artemis Fowl" for Andy Weir's "Artemis", and a different author's "Revival".

Replaces it with CanAutomaticallySelectFallback, following the maintainer's
conservative candidate: parse only "Title" or "Author - Title" layouts (with
optional trailing year/format suffixes), require the complete remaining title
to match the book exactly after normalization, and require at least one
corroborating signal (parsed author, Artist field, or ASIN) with any
conflicting evidence vetoing. The Artist field is only corroboration when it
matches (it can be a provider guess). Uncertain layouts and uncorroborated
authorless results are skipped, so this won't recover every title-only
release but refuses to grab a different book.

Tests cover a valid author-matched fallback, both reported collisions
(same-word title and same-title/different-author), an uncorroborated
authorless skip, and Artist-field corroboration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The classifier tests stop at ConstrainFallbackResultsToTitle. Add tests
that drive the whole automatic-search cycle (AutomaticSearchProcessor
.RunCycleAsync) through to the download-service handoff:

- A same-word different book (Eoin Colfer's Artemis Fowl for Andy Weir's
  Artemis) never reaches StartDownloadAsync.
- A same-title different author (Chris Weitz's Revival for Stephen King's)
  never reaches StartDownloadAsync.
- A correctly attributed fallback (Andy Weir - Artemis) does reach
  StartDownloadAsync with the requested book's id.

Both collisions were reported reaching download selection in the live
container pass; these lock that down at the cycle level.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@therobbiedavis
therobbiedavis force-pushed the fix/search-author-fallback branch from 9d27813 to 9882668 Compare October 25, 2026 04:26
@therobbiedavis
therobbiedavis merged commit 5564913 into Listenarrs:canary Oct 25, 2026
7 checks passed
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Oct 25, 2026
…v1.5.2)

Upstream v1.3.24-v1.5.2: Listenarrs#757 indexer-timeout containment, Listenarrs#605 quality-profile
picker, Listenarrs#714 automatic-search timing (sweep interval / per-book cooldown / book
delay), Listenarrs#1072 author-free retry with title constraint, Listenarrs#990 post-nominal author
folders.

Resolutions:
- ApplicationSettings / AutomaticSearchService / AutomaticSearchResultClassifier:
  upstream's three timing columns and author-free fallback replace the fork's
  AutomaticSearchIntervalHours-as-cooldown, trailing book delay and opt-out
  title-only fallback. Fork's SearchProgressReporter broadcasts, blocked-release
  and filter-pipeline hooks kept; SearchNow.cs no longer passes the fallback flag.
- Migrations: fork-only 20261021212417_RetireForkAutomaticSearchTuning renames the
  July fork columns to Retired_* (native transactional ALTER, no rebuild) so
  upstream's 20261021212418_AddAutomaticSearchTiming applies unchanged. Snapshot =
  upstream's. SqliteMigrationSchemaTests expected history updated.
- IndexerSearchWorkflow: fork's inner HttpClient-timeout catch removed in favor of
  upstream's per-indexer OperationCanceledException containment (Listenarrs#757 supersedes
  Listenarrs#967); the contradicted cooperative-cancellation test removed. Torznab provider
  catch and HttpClientTimeoutClassifier remain.
- WantedView.vue: fork's server-side wanted-search strip/queue kept alongside
  Listenarrs#605's profile-recovery banner, modal and pending-refresh set; upstream's
  browser-loop remnants (sleep/abortDelay/showConfirmSearchAll) not reintroduced;
  duplicated useToast import collapsed.
- AutomaticSearchProcessorFallbackTests: scorer mock adapted to the fork's
  three-parameter ScoreSearchResults.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch patch version bump - backward compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-generated search query breaks on hyphenated titles (-word treated as exclusion by Newznab indexers)

3 participants