Skip to content

fix: Search the Newznab categories you configured - #669

Merged
frankieramirez merged 2 commits into
mainfrom
fix/provider-config-fidelity
Aug 12, 2026
Merged

fix: Search the Newznab categories you configured#669
frankieramirez merged 2 commits into
mainfrom
fix/provider-config-fidelity

Conversation

@frankieramirez

@frankieramirez frankieramirez commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Three follow-ups surfaced while documenting Torznab configuration for #668 (docs PR: frankieramirez/comicarr-docs#9). All three were verified against the code before fixing.

1 — Newznab categories never reached the searcher

Field 5 of a Newznab record is uid#categories: the uid is the i= parameter of the indexer's RSS URL, the rest is the category list. The Settings Categories box wrote straight into that field, mapping commas to # — so 7030,7020 was stored as 7030#7020, which reads as uid 7030 with 7020 as the only category.

It got worse on the way to runtime. get_extras() rewrote the field — # to ,, then a leading , back to # — and search.py decides whether any category was configured by testing for #:

if "#" in newznab_host[4].rstrip():
    catstart = newznab_host[4].find("#")
    category_newznab = re.sub("#", ",", newznab_host[4][catstart + 1:]).strip()
else:
    category_newznab = "7030"

A stored 1#7030 arrived as 1,7030, failed that test, and fell through to the built-in 7030. Every Newznab search used 7030 regardless of configuration, unless the stored value already began with # — the one shape that survived the round trip. rsscheck.py was getting 1,7030 as the RSS uid at the same time.

Fixed by removing the rewrite so the storage contract is read the same way by search.py, rsscheck.py, and the providers API, and by giving the uid its own field in Settings so the Categories box means categories:

Before After
Typed 7030,7020 stored 7030#7020, searched 7030 (default) stored 1#7030#7020, searches 7030,7020
Displayed back as 7030,7020 — a claim the search did not honour 7030,7020, with RSS user ID 1 beside it
New indexer default 5030 — a TV category 7030 — Books/Comics

Existing indexers are re-read on upgrade. If the box now shows fewer categories than you remember typing, that is the part that was actually reaching your indexer; the rest was being discarded. Editing categories can no longer repoint the RSS feed at a different user — the existing uid is preserved when the client does not send one.

2 — Provider booleans were tolerated at the edge and disagreed on in the middle

_PROVIDER_BOOLEAN_VALUES accepts eight spellings for the verify TLS and enabled fields. The consumers do not:

Reader Verify Enabled
search.py bool(int(field))ValueError on "True" field == "1"
rsscheck.py bool(int(field)) str(field) == "1"
health.py, providers API accepts true / yes / on

So an entry stored as True was reported enabled on the Acquisition tab and skipped by the searcher — the health surface claiming a route was ready while nothing was querying it. A non-numeric verify took the search down outright.

Both fields are now canonicalised to "1"/"0" in parse_provider_extras, the single boundary every reader and writer passes through, so edge tolerance cannot become disagreement downstream.

This shape was reachable. The legacy-torznab absorption added in #635 passed self.TORZNAB_VERIFY through as a raw Python bool; it serialised to "True" and came back as "True" on the next startup. So an absorbed provider worked once and then broke every subsequent search. It now writes "1"/"0" at the append site too.

3 — TORZNAB_VERIFY defaulted to False

That value exists only to seed the verify field of an absorbed torznab_* entry. Every other field of such an entry comes from the operator — so a False here silently downgraded TLS verification on a provider nobody chose to downgrade. Now True.

Also

The absorption's log messages told operators to "configure the provider via the Settings UI (extra_torznabs)". There is no Torznab editor — Settings → Search edits Newznab only, so anyone following that message found nothing. They now name extra_torznabs under [Torznab] in config.ini and show the entry format inline.

Testing

New tests: Newznab category round-trip through the providers service, uid preservation on edit, uid-alone storage when categories are cleared, Torznab's lack of a uid prefix, boolean canonicalisation across all eight spellings and through serialisation, absorbed-entry verify defaults and reread, unmangled runtime categories, and the new Settings field end to end.

Two existing tests changed and both encoded the bug: one asserted the projection reported 5030,7000 for a stored 5030#7000 — the projection agreeing with the operator's intent rather than with the search — and the other predates rss_uid.

  • uv run pytest tests/unit — 2414 passed
  • npm run test:run (frontend) — 438 passed
  • npm run typecheck — clean
  • npm run lint — all lanes green (modern, guards, backend, generated, frontend + prettier)

🤖 Generated with Claude Code

https://claude.ai/code/session_01QjqbhfDFmmMeBvn5Pqbk4z

Summary by CodeRabbit

  • New Features
    • Added a separate RSS user ID field for Newznab indexers.
    • New indexers now default to category 7030, with updated guidance for category configuration.
  • Bug Fixes
    • Editing Newznab categories now preserves the RSS user ID.
    • Stored category values retain their formatting and multiple categories.
    • Legacy Torznab settings now use consistent boolean values and enable TLS verification by default.
    • Updated guidance clarifies which Torznab configuration fields are inactive.

Three follow-ups from the documentation pass on #668, all found while
checking what the Search Providers page should actually say.

1. Newznab categories never reached the searcher. Field 5 of a Newznab
   record is `uid#categories`, but the Settings Categories box wrote
   straight into it, so `7030,7020` was stored as `7030#7020` -- uid 7030,
   one category. Worse, `get_extras()` rewrote the field on its way to
   runtime ('#' to ',', then the leading ',' back to '#'), leaving a shape
   `search.py` reads as "no category configured". Every Usenet query fell
   back to the built-in 7030 unless the stored value happened to begin
   with '#'. The rewrite is gone, the uid has its own Settings field, and
   the categories box means categories. New indexers default to 7030
   (Books/Comics) rather than 5030, a TV category.

2. Provider booleans were tolerated at the edge and disagreed on in the
   middle. `_PROVIDER_BOOLEAN_VALUES` accepts eight spellings; `search.py`
   reads verify as `bool(int(field))` and filters enabled with `== "1"`,
   while `health.py` and the providers API accept true/yes/on. An entry
   stored as `True` was therefore reported enabled by the Acquisition tab
   and skipped by the searcher, and a non-numeric verify raised
   ValueError mid-search. Both fields are canonicalised in
   `parse_provider_extras`, the one boundary every reader and writer
   passes through.

   The legacy-torznab absorption added in #635 wrote exactly that shape:
   it passed `self.TORZNAB_VERIFY` through as a raw bool, which serialised
   to "True" and came back as "True" on the next startup. It now writes
   "1"/"0" itself as well.

3. `TORZNAB_VERIFY` defaulted to False, so an absorbed legacy entry
   inherited every field from the operator except TLS verification, which
   it silently turned off. Now True.

Also: the absorption's log messages pointed operators at "the Settings UI
(extra_torznabs)". There is no Torznab editor -- Settings/Search edits
Newznab only. They now name `extra_torznabs` under `[Torznab]` in
config.ini and show the entry format.

Docs for all of this: frankieramirez/comicarr-docs#9.

Tests: category round-trip and uid preservation through the providers
service, boolean canonicalisation across all eight spellings and through
serialisation, absorbed-entry verify defaults, unmangled runtime
categories, and the new Settings field. 2414 backend and 438 frontend
tests pass; `npm run lint` green on all lanes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjqbhfDFmmMeBvn5Pqbk4z
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 207996b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
comicarr Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@frankieramirez, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 26e96b31-efc5-47bb-9a5e-6921a8cbd0a4

📥 Commits

Reviewing files that changed from the base of the PR and between c6073e6 and 207996b.

📒 Files selected for processing (2)
  • comicarr/app/system/service.py
  • tests/unit/test_system_domain.py
📝 Walkthrough

Walkthrough

Newznab providers now store RSS user IDs separately from categories. Provider updates preserve this ID while editing categories. Provider booleans use canonical values, and absorbed legacy Torznab entries default to TLS verification.

Changes

Provider configuration updates

Layer / File(s) Summary
Newznab category and RSS UID flow
comicarr/app/system/service.py, comicarr/config.py, frontend/src/..., tests/unit/...
Newznab uid#categories values are split and rebuilt during provider projection and updates. The frontend exposes separate category and RSS user ID fields. Stored category formatting remains unchanged.
Legacy provider normalization
comicarr/app/config/registry.py, comicarr/config.py, tests/unit/..., .changeset/...
Provider boolean values are normalized to "1" or "0". Legacy Torznab migration enables TLS verification by default and updates configuration guidance.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

A rabbit checks the categories bright,
Keeps the RSS ID tucked just right.
TLS now guards the trail,
Booleans line up without fail,
And searches find the proper delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: ensuring configured Newznab categories are used during searches.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/provider-config-fidelity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@comicarr/config.py`:
- Around line 658-661: Update the legacy Torznab migration log in the
surrounding configuration migration logic to use the approved logger.fdebug(...)
method instead of logger.info(...), preserving the existing message and
formatting.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e614a07a-b5b8-44b7-817f-d62ec96b38c2

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2894c and c6073e6.

📒 Files selected for processing (9)
  • .changeset/newznab-categories-actually-searched.md
  • comicarr/app/config/registry.py
  • comicarr/app/system/service.py
  • comicarr/config.py
  • frontend/src/components/settings/SearchTab.tsx
  • frontend/src/types/config.ts
  • frontend/tests/pages/SettingsPage.test.ts
  • tests/unit/test_config_version_migrations.py
  • tests/unit/test_system_domain.py

Comment thread comicarr/config.py
`7030, 7020` was stored with the space intact and would have reached the
indexer as `cat=7030, 7020`. It never showed before because the categories
were not reaching the searcher at all; now that they are, the field has to
tolerate padding and empty entries.

Applies to both provider types and to the projection, so what Settings
displays, what is stored, and what is queried are the same list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjqbhfDFmmMeBvn5Pqbk4z
@frankieramirez
frankieramirez merged commit 1dd8dc7 into main Aug 12, 2026
16 checks passed
@frankieramirez
frankieramirez deleted the fix/provider-config-fidelity branch August 12, 2026 12:09
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