Repository navigation
Conversation
…sure age in UTC Indexer.MinimumAge and Indexer.MaximumSize save and reload and were read by nothing. Both are now applied in SearchResultScorer, beside Retention, which was already read there. MaximumSize rejects a result larger than the indexer's own ceiling. It is deliberately outside the existing size block, which is skipped for Usenet and reads QualityProfile.MaximumSize. That is a different setting with the same name, which is worth saying out loud because grepping MaximumSize finds the profile one and makes the indexer field look wired. MinimumAge rejects a Usenet post that has not been up long enough. The reason is propagation rather than preference: grabbing too early produces an incomplete or failed download. It does not apply to torrents, where a fresh post is complete. Two things fell out of doing this. The indexer lookup moved above the size gate, because all three settings need it. That also fixes an ordering bug: the lookup is what corrects isNzb from the indexer's own type, and it used to run after the size gate, so a Usenet result recognised only by its indexer type was size-checked despite the exemption immediately below it. Age was measured in the wrong timezone. The published date went through a bare DateTime.TryParse, which converts a trailing Z to the host's local time and returns Kind=Local, and the result was then subtracted from DateTime.UtcNow. Every age was out by the server's UTC offset, so results looked older west of UTC and newer east of it. At day granularity that usually only matters at a boundary; at the minute granularity MinimumAge needs, it decides the answer. It is parsed to UTC explicitly now, with AssumeUniversal for indexer dates that carry no offset. There is a test for it that does not depend on where it runs. Indexer.EnableRss is the third field in the report and is untouched. It advertises RSS sync, and there is no RSS sync anywhere in the codebase to enable, so wiring it would mean building the feature and removing it would presume you do not intend to. That one is a question rather than a fix, and I would rather ask it separately. Controls: reverting the UTC parse, the size gate or the age gate each fails its own test and leaves the others green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 1, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 1, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 1, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 1, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 2, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 3, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 3, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 3, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 4, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 4, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 9, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 10, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 10, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 10, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 10, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
m4bard
added a commit
to m4bard/Listenarr
that referenced
this pull request
Sep 10, 2026
LOCAL ONLY. Not part of Listenarrs#863 or Listenarrs#921 and deliberately not offered to either. Both PRs grow SearchResultScorer.cs. Listenarrs#863 adds the batch-resolved indexer dictionary, Listenarrs#921 hoists the indexer lookup above the size gate and reads two more fields from it. Canary is 457 lines, Listenarrs#863 alone 476, Listenarrs#921 alone 498, and the two together 517. ActiveProductionSourceFiles_RemainFocused caps production files at 500, so each passes alone and only the combination fails. This moves the indexer lookup and IsNzbResult into a partial, which takes the main file to 473. It is a stack commit rather than a change to either pull request because the reason it exists is how our local stack combines them, which is not visible from either PR and not the maintainer's problem to review. Putting it in Listenarrs#921 would also mean hand-porting Listenarrs#863's resolved-indexer logic into a file Listenarrs#863 knows nothing about on every rebuild, since rerere can only replay a resolution for a conflicted hunk, not an edit to a new file. If both PRs merge upstream, canary itself crosses the cap and this becomes a real follow-up rather than a local patch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YEVQ7qDJLk5196MFeggWuA (cherry picked from commit f58964137bfb6a44650de793d541e17f87e87a7c) (cherry picked from commit d4026e72e7260482bfe09865cc1721239f062e2f)
The published-date test this PR added cannot fail on a host whose local time is UTC, which is every hosted CI runner. Measured rather than argued: with the DateTimeStyles reverted to a bare TryParse, the whole of QualityProfileScoringTests passes under TZ=UTC and the offset test fails under TZ=America/Chicago. The bug is TryParse returning the host's local time with Kind=Local, and on a UTC host the local time and the UTC time are the same instant, so an assertion about the instant cannot see it. The Kind differs on every host. So the parse moves into an internal TryParsePublishedDateUtc that a test can call, and SearchResultScorerPublishedDateTests asserts both the instant and the Kind for an offset string, a Z string and a bare one. With the styles reverted those three rows fail under TZ=UTC with Local, Local and Unspecified. SearchResultScorer.cs was at 498 lines against the 500-line cap the architecture tests enforce, so there was nowhere to put the method. The class becomes partial and the title and profile detection helpers move to SearchResultScorer.Helpers.cs, which leaves the main file at 413 lines and the new parse in a file of its own. No behaviour changes: the helpers move unedited, and the parse does exactly what the inline expression did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The indexer's Maximum Size gate sits above the block that skips size checks for Usenet, so unlike the profile setting of the same name it applies to both protocols. That is deliberate and it matches how Sonarr and Radarr treat their equivalent, but the only test rows were torrents and the inline comment beside the gate argues the other way about the profile setting, so nothing stopped a later reader moving the gate inside the !isNzb block and calling it a tidy-up. The size theory gains a download-type parameter and two nzb rows. Control: adding !isNzb to the gate fails the new rejection row and leaves the three others green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BackendArchitectureTests.TestClasses_FollowRepositoryConventions requires every test class to inherit BaseTests and carry an exact Name trait and a non-empty Category trait. The new class had none of the three, which the focused filter could not show and the full suite did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m4bard
force-pushed
the
fix/899-indexer-age-and-size
branch
from
September 15, 2026 18:35
4d7f6cc to
4540e62
Compare
… parallel ScoreSearchResults fans out with Task.WhenAll, and each task built a SearchResultScorer over the same scoped IIndexerRepository and queried it for the result's indexer. IIndexerRepository is scoped and the ListenArrDbContext behind it is scoped, so a batch of N results issued N concurrent queries against one context. EF rejects a second operation started on a context while another is in flight. The symptom is not a failed request. The scorer catches the exception and logs at Debug, leaving indexerRetention at 0 and skipping the Usenet detection that sets isNzb. So age and retention checks silently score against the wrong assumptions for whichever results lost the race, and the batch comes back plausibly ordered and quietly wrong. Resolve each distinct indexer once, sequentially, before fanning out, and pass the results into the scorer. Three callers reach this: the quality profile controller, download submission, and the six-hour automatic search sweep. This is also fewer queries rather than merely safer ones. A batch commonly carries many results across a handful of indexers, so it goes from one query per result to one per distinct indexer. IndexerSearchWorkflow already fetches its indexers before its own fan-out, so this makes the two agree. The single-result ScoreSearchResult keeps its old behaviour and still queries, since there is nothing to batch there. Asserting on the EF exception would mean racing it, so the test counts the overlap directly: a stub repository records the highest number of calls in flight at once. It reports 12 without the change and 1 with it, and also pins the query count at one per distinct indexer rather than one per result.
…corer The concurrency test asserts the batch makes three non-overlapping lookups for twelve results. Both of those are properties of the lookup, not of the use. Pass an empty dictionary to the scorer instead of the resolved one and the test still sees three non-overlapping calls and still passes, while every result silently loses its indexer retention and its Usenet detection. That is the same quietly wrong scoring the change exists to prevent, so the regression the test would have to catch is the one it cannot see. The new case gives the stub a ten day retention and a result published a hundred days ago, with the profile's MaximumAge set well beyond that so nothing else in the scorer can produce the rejection. An ignored or empty dictionary leaves retention at zero and the result accepted. No production code changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
QualityProfile.MinimumSize and MaximumSize are int megabytes, and the scorer multiplied them by 1024 * 1024 in int arithmetic. 2048 MB is one byte past int.MaxValue once multiplied, so a profile capped at 2 GB or more wraps negative and the two gates fail in opposite directions: every release is "larger than" a negative maximum and is rejected as too large, and nothing is "smaller than" a negative minimum so the minimum silently stops applying. The quality profile form puts no upper bound on either input, and a 2 GB ceiling on an audiobook profile is an ordinary thing to set. The fix is the cast the indexer size gate one block above already uses. Both theories carry a control row below the overflow, so a fix that simply stopped rejecting, or simply started rejecting, fails them. Without the casts the four overflow rows fail and the two control rows pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR now carries three fixes that all land in
SearchResultScorer.csand its partials, previously filed as three separate PRs: this one (#921), #863, and #985. #921 already said it would absorb #863 ("This conflicts with my own #863 in SearchResultScorer.cs... Whichever of the two lands first, I will rebase the other"), and #985 was carved out of #921's own work ("Found while wiring Indexer.MaximumSize in #921 and kept out of that PR as a separate bug"). Consolidating avoids two rebases of the same file for the same author, and #863 and #985 are being closed in favor of the commits here rather than abandoned; see the closing comments on each.Change 1 (from #863): resolve indexers once per batch, not once per result in parallel
QualityProfileService.ScoreSearchResultsfanned out withTask.WhenAll, and each task queried the same scopedIIndexerRepository, so a batch of N results issued N concurrent queries against one scopedDbContext. EF rejects a second operation on a context while another is in flight.The scorer already caught that exception and logged it at Debug, so nothing failed outright. What it lost was
indexerRetention, which stayed 0, and the branch that setsisNzbfrom the indexer's own type. Age and retention checks then scored the result against assumptions that did not match its indexer, and the batch came back plausibly ordered and quietly wrong.ScoreSearchResultsnow resolves the batch's distinct indexers sequentially up front (ResolveIndexersAsync) and passes the lookup intoSearchResultScorer, which prefers it over querying when present. The single-resultScoreSearchResultpath is unchanged and still queries directly, since there is nothing to batch there. Three callers reach the batch path: the quality profile controller, download submission, and the six-hour automatic search sweep.This is fewer queries as well as safer ones: a batch commonly spans many results across a handful of indexers, so it goes from one query per result to one per distinct indexer.
IndexerSearchWorkflowalready resolves its indexers before its own fan-out, so this brings the two in line.Tested by counting the overlap directly rather than racing the EF exception: a stub
IIndexerRepositoryrecords the highest number of calls it ever had in flight at once. It reports 12 without the change and 1 with it, over twelve results across three indexers, and pins the call count at 3 so a regression that restores per-result querying fails even if it happens not to overlap on a given run.Change 2 (from #921): read Indexer.MinimumAge and Indexer.MaximumSize, and measure age in UTC
Fixes two of the three fields in #899, and leaves the third alone on purpose.
Indexer.MinimumAgeandIndexer.MaximumSizesaved and reloaded and were read by nothing. Both are now applied inSearchResultScorer, besideRetention, which was already read there.MaximumSizerejects a result larger than the indexer's own ceiling. It sits outside the existing size block, which is skipped for Usenet and readsQualityProfile.MaximumSize, a different setting with the same name.MinimumAgerejects a Usenet post that has not been up long enough, since grabbing too early tends to produce an incomplete or failed download; it does not apply to torrents, where a fresh post is complete.Two things fell out of doing this. First, the indexer lookup moved above the size gate, since all three settings need it; that also fixes an ordering bug where a Usenet result recognized only by its indexer type was size-checked before the type correction that exempts it ran. Second, age was measured in the wrong timezone: the published date went through a bare
DateTime.TryParse, which turns a trailingZinto the host's local time withKind=Local, then got subtracted fromDateTime.UtcNow. Every age was off by the server's UTC offset. It is parsed to UTC explicitly now, withAssumeUniversalfor indexer dates carrying no offset.SearchResultScorer.cswas at 498 lines against the architecture tests' 500-line cap, so the new UTC parse (TryParsePublishedDateUtc) and the title/profile detection helpers moved intoSearchResultScorer.Helpers.csandSearchResultScorer.PublishedDate.cs, both partials of the same class. No behavior changed in the move; the helpers moved unedited and the parse does exactly what the inline expression did.Indexer.EnableRssis untouched. It advertises RSS sync, and there is no RSS sync anywhere in the codebase to enable; wiring it would mean building the feature, and removing it would presume the intent is to drop it. That is a question rather than a fix, raised separately.Six tests added, each with an opposite case so none of them can pass against an implementation that always rejects. Reverting the UTC parse, the size gate, or the age gate each fails only its own test and leaves the rest green.
Change 3 (from #985): compute the profile size gates in long, not int
QualityProfile.MinimumSizeandMaximumSizeareintmegabytes, and the scorer multiplied them by1024 * 1024inintarithmetic before comparing against a result'slongbyte size. At 2048 MB the multiply overflowsintand wraps to a negative number, so the two gates fail in opposite directions: Maximum rejects every release, since everything is larger than a negative ceiling, and Minimum stops rejecting anything, since nothing is smaller than a negative floor. The settings form puts no upper bound on either field, and a 2 GB ceiling on an audiobook profile is ordinary.The fix is the same
(long)cast the indexer size gate a few lines above already uses, applied to both profile lines. Two tests added, each with a control row below the overflow threshold, so a change that simply stopped rejecting or simply started rejecting fails the control.How the three fit together
All three touch
SearchResultScorer.csdirectly; #921 also touches its two partials, #863 also touchesQualityProfileService.cs. The indexer-resolution change (#863) and the age/size/UTC change (#921) hit the same lines for different reasons, which is the conflict #921's original body flagged; #985's cast fix sits in the untouched profile size block a few lines below and does not interact with either. Nothing about combining them changes behavior beyond what each already documented on its own; there is no fourth interaction to call out.Testing
Full backend suite run twice: on this PR's own branch before folding #863 and #985 in, and again after. Before: 3132 passed, 0 failed, 130 skipped. After: 3140 passed, 0 failed, 130 skipped. The 8 new passes are the concurrency test from #863 and the two overflow tests from #985; nothing else moved.
Disclosure: drafted with Claude Code at my direction; I read the cited code at the stated commit and reviewed this before posting.