Skip to content

fix: make blocked search setup actionable - #596

Merged
frankieramirez merged 2 commits into
mainfrom
frankieramirez/investigate-github-issue-589
Aug 9, 2026
Merged

fix: make blocked search setup actionable#596
frankieramirez merged 2 commits into
mainfrom
frankieramirez/investigate-github-issue-589

Conversation

@frankieramirez

@frankieramirez frankieramirez commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • add editable Newznab indexer and SABnzbd configuration to Settings
  • distinguish missing, disabled, and unusable acquisition providers and clients
  • link blocked searches directly to the relevant settings section
  • show configured, executable, and attempted provider counts in acquisition health
  • document the corresponding sample configuration

Root cause

Issue #589 showed the NZB route as disabled even when SABnzbd itself was ready. The backend also requires an enabled Newznab provider, but the modern UI did not expose that configuration and surfaced only a generic disabled state. Users therefore could not identify or repair the actual blocker from the web interface.

Safety and reliability

  • keep provider and SAB API keys write-only in API responses
  • bind retained credentials to the original HTTP origin so changing a server requires entering its key again
  • persist provider rows and global enablement atomically
  • validate provider URLs and boolean enablement values
  • redact provider and SAB credentials from outbound request errors
  • warn about unsaved indexer edits and guard navigation away from them

Validation

  • uv run pytest tests/unit -v — 2,156 passed
  • npm run test:run — 389 passed
  • uv run npm run lint — backend/frontend lint, formatting, generated config types, and guards passed
  • frontend TypeScript typecheck passed
  • CodeRabbit review completed with no findings

Fixes #589

Summary by CodeRabbit

  • New Features

    • Added editable SABnzbd download client settings.
    • Added Newznab indexer management, including add, edit, remove, reset, and save actions.
    • Added clearer acquisition health counts and actionable setup links.
    • Added warnings for unsaved indexer changes and navigation protection.
  • Bug Fixes

    • Improved provider and downloader readiness diagnostics.
    • Preserved existing API keys when settings remain unchanged.
    • Redacted sensitive credentials from error logs and configuration displays.
    • Added validation when changing SABnzbd or indexer server addresses.

@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ef8f7cc

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 9, 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: 34 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: a49e477e-696a-4942-bbfa-bb2d6ca2ec0b

📥 Commits

Reviewing files that changed from the base of the PR and between 2b398f6 and ef8f7cc.

📒 Files selected for processing (10)
  • .changeset/kind-geckos-search.md
  • comicarr/app/system/service.py
  • comicarr/sabnzbd.py
  • comicarr/search.py
  • frontend/src/components/settings/SearchTab.tsx
  • frontend/src/lib/httpOrigin.ts
  • frontend/src/pages/SettingsPage.tsx
  • frontend/tests/pages/SettingsPage.test.ts
  • tests/unit/test_sab_addfile_handoff.py
  • tests/unit/test_system_domain.py
📝 Walkthrough

Walkthrough

The change adds editable NZB and Newznab settings, transactional provider persistence, credential sanitization, route-specific health diagnostics, and actionable frontend guidance for search and download configuration failures.

Changes

Provider configuration

Layer / File(s) Summary
Provider API and transactional persistence
comicarr/app/config/registry.py, comicarr/app/system/..., comicarr/config.py, config.ini.sample, tests/unit/test_system_domain.py
Provider endpoints validate HTTP(S) origins, preserve credentials and IDs, sanitize responses, and persist provider and scalar settings in one transaction.
Editable provider and downloader settings
frontend/src/types/..., frontend/src/hooks/useConfig.ts, frontend/src/components/settings/..., frontend/src/pages/SettingsPage.tsx, frontend/src/lib/configSave.ts, frontend/tests/pages/SettingsPage.test.ts
The settings UI edits SABnzbd and Newznab values, preserves stored keys, tracks unsaved provider changes, validates origin changes, and submits changed settings.
Route health diagnostics and guidance
comicarr/app/search/health.py, frontend/src/components/settings/AcquisitionHealthTab.tsx, frontend/src/lib/healthBand.ts, frontend/src/pages/SeriesDetailPage.tsx, related tests
NZB routes report configured, executable, and attempted counts. Blocking reasons now distinguish missing or disabled providers and downloaders, with links to the relevant settings.
Credential-safe request logging
comicarr/sabnzbd.py, comicarr/search.py, tests/unit/test_sab_addfile_handoff.py
Request exception logs redact SABnzbd and provider API keys.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsPage
  participant SearchTab
  participant useConfig
  participant system_router
  participant system_service
  participant Database
  SettingsPage->>SearchTab: Open search settings
  SearchTab->>useConfig: Fetch provider configuration
  useConfig->>system_router: GET /api/config/providers
  system_router->>system_service: Get sanitized providers
  system_service-->>useConfig: Provider identities and key status
  SearchTab->>useConfig: Save enabled state and providers
  useConfig->>system_router: Update provider configuration
  system_router->>system_service: Validate provider payload
  system_service->>Database: Persist providers and enablement
  Database-->>system_service: Transaction result
  system_service-->>SearchTab: Sanitized provider data
Loading

Possibly related PRs

Poem

A rabbit checks the NZB trail,
Keys stay hidden in the mail.
Providers show where routes may go,
Settings guide the fix to know.
With tidy logs and health in view,
The download path is clear anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
Linked Issues check ✅ Passed The PR addresses [#589] by identifying NZB blockers, exposing required settings, and linking users to the relevant configuration sections.
Out of Scope Changes check ✅ Passed The changes support [#589] by implementing configuration, diagnostics, navigation guidance, credential protection, and persistence for actionable NZB setup.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: making blocked search setup actionable for users.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch frankieramirez/investigate-github-issue-589

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.

@frankieramirez
frankieramirez marked this pull request as ready for review August 9, 2026 22:04
@frankieramirez frankieramirez changed the title Make blocked search setup actionable fix: make blocked search setup actionable Aug 9, 2026

@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: 4

🧹 Nitpick comments (5)
comicarr/app/config/registry.py (1)

372-387: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider validating the NZB_DOWNLOADER enum on write.

NZB_DOWNLOADER is now writable through the settings API. update_config filters keys but does not check the value range. process_kwargs coerces any digit string to int, so a payload such as {"nzb_downloader": 9} persists an unmapped value. configure() then leaves USE_SABNZBD, USE_NZBGET, and USE_BLACKHOLE all False, and get_safe_config reports the label "None". A server-side range check keeps the stored enum inside 0-3.

🤖 Prompt for 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.

In `@comicarr/app/config/registry.py` around lines 372 - 387, Add server-side
validation for NZB_DOWNLOADER in the update_config/process_kwargs write path,
allowing only integer values from 0 through 3 before persisting the setting.
Reject out-of-range values such as 9 while preserving valid updates and the
existing ConfigKey behavior.
tests/unit/test_system_domain.py (1)

811-833: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The api_key_set assertion does not prove post-persistence state.

apply_transaction is mocked, so ctx.config.EXTRA_NEWZNABS still holds the pre-update tuple that contains "secret". _safe_provider_projection reads that same attribute, so line 833 passes even if the service dropped the credential. The persisted-value assertion on line 831 is the one that carries the guarantee.

Set ctx.config.EXTRA_NEWZNABS from the captured apply_transaction argument before asserting the projection, or drop line 833.

Add a case where the row omits id and the stored host contains userinfo. That path exercises the by_identity fallback flagged in comicarr/app/system/service.py.

🤖 Prompt for 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.

In `@tests/unit/test_system_domain.py` around lines 811 - 833, Update
test_update_providers_accepts_safe_objects_and_preserves_blank_existing_key so
the mocked configuration reflects the captured EXTRA_NEWZNABS transaction before
asserting result["providers"][0]["api_key_set"], or remove that assertion. Add a
test case with a provider row lacking id and a stored host containing userinfo
to exercise the by_identity fallback in update_providers.
frontend/tests/pages/SettingsPage.test.ts (1)

121-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the global stub in a teardown hook.

vi.unstubAllGlobals() runs inline at line 126. If any assertion between lines 122 and 125 fails, the test aborts and the confirm stub leaks into the remaining tests in this file.

Register the restore in afterEach so it always runs.

♻️ Proposed teardown hook
 describe("settings configuration", () => {
+  afterEach(() => {
+    vi.unstubAllGlobals();
+  });
+

Also import afterEach from vitest and remove the inline call at line 126.

🤖 Prompt for 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.

In `@frontend/tests/pages/SettingsPage.test.ts` around lines 121 - 126, Import
afterEach from vitest and register vi.unstubAllGlobals() in an afterEach
teardown for SettingsPage tests. Remove the inline teardown call from the test
containing the confirm stub, while preserving the existing assertions and stub
setup.
frontend/src/pages/SettingsPage.tsx (2)

59-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

httpOrigin is defined twice with different signatures. Both files implement the same HTTP/HTTPS origin parsing, and both use it to decide whether a stored API key must be re-entered. The signatures already differ, so the two credential-rebinding checks can drift apart.

  • frontend/src/pages/SettingsPage.tsx#L59-L68: move this implementation into a shared module under frontend/src/lib/ and import it here.
  • frontend/src/components/settings/SearchTab.tsx#L32-L41: delete the local copy and import the shared helper.

As per coding guidelines: "Place frontend pages and components under frontend/src/, and put API-client code in frontend/src/lib/."

🤖 Prompt for 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.

In `@frontend/src/pages/SettingsPage.tsx` around lines 59 - 68, Move the shared
httpOrigin implementation from frontend/src/pages/SettingsPage.tsx:59-68 into a
module under frontend/src/lib/, then import and use it in SettingsPage.tsx.
Remove the duplicate local implementation from
frontend/src/components/settings/SearchTab.tsx:32-41 and import the shared
helper there, preserving both credential-rebinding checks.

Source: Coding guidelines


91-98: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Block SPA navigation when indexer changes are dirty.

providerDirty only checks section links already rendered in Settings, and beforeunload only covers full unload. A /settings route change from AppSidebar or useNavigate("/settings") can remove the component before the indexed form is saved, causing edits to be discarded without a prompt. Add React Router v7 block/confirm behavior while providerDirty is true.

🤖 Prompt for 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.

In `@frontend/src/pages/SettingsPage.tsx` around lines 91 - 98, Update the
SettingsPage navigation guard around providerDirty to use React Router v7’s
blocker/confirmation behavior, so any SPA navigation that would unmount the
settings form is blocked while unsaved indexer changes exist. Preserve the
existing section-link prompt, allow navigation after confirmation, and ensure
non-dirty navigation remains unaffected.
🤖 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/app/system/service.py`:
- Around line 483-509: Update the by_identity construction in the object_payload
provider normalization flow to key legacy entries by the sanitized host produced
by _safe_provider_host, while retaining the stored name and existing six-field
eligibility. Ensure the lookup using row.get("name") and row.get("host") matches
the client projection so credential preservation works when no id is present.

In `@comicarr/search.py`:
- Around line 1343-1362: Prefix each changed backend log message with its
required stable bracketed context: in comicarr/search.py lines 1343-1362, update
the timeout, connection-error, and request-error logs to use [NZB-SEARCH]; in
comicarr/sabnzbd.py lines 83-86, prefix the sender failure with [SAB-SEND]; and
in comicarr/sabnzbd.py lines 131-133, prefix the queue-monitor failure with
[SAB-QUEUE].

In `@frontend/src/components/settings/SearchTab.tsx`:
- Around line 194-284: Update the suffix calculation in the providers.map render
block so each row’s field IDs are unique even when saved and newly added
indexers share numeric values. Use a stable per-row identifier that cannot
collide, then keep the existing suffix-based id and htmlFor construction
unchanged.

In `@frontend/src/pages/SettingsPage.tsx`:
- Around line 176-188: Update the SABnzbd validation block in the settings form
to run only when SABnzbd is the selected NZB client and sab_host has a non-empty
value. Preserve the existing URL and API-key-change checks within that scope, so
unrelated saves and empty stored hosts bypass SABnzbd validation.

---

Nitpick comments:
In `@comicarr/app/config/registry.py`:
- Around line 372-387: Add server-side validation for NZB_DOWNLOADER in the
update_config/process_kwargs write path, allowing only integer values from 0
through 3 before persisting the setting. Reject out-of-range values such as 9
while preserving valid updates and the existing ConfigKey behavior.

In `@frontend/src/pages/SettingsPage.tsx`:
- Around line 59-68: Move the shared httpOrigin implementation from
frontend/src/pages/SettingsPage.tsx:59-68 into a module under frontend/src/lib/,
then import and use it in SettingsPage.tsx. Remove the duplicate local
implementation from frontend/src/components/settings/SearchTab.tsx:32-41 and
import the shared helper there, preserving both credential-rebinding checks.
- Around line 91-98: Update the SettingsPage navigation guard around
providerDirty to use React Router v7’s blocker/confirmation behavior, so any SPA
navigation that would unmount the settings form is blocked while unsaved indexer
changes exist. Preserve the existing section-link prompt, allow navigation after
confirmation, and ensure non-dirty navigation remains unaffected.

In `@frontend/tests/pages/SettingsPage.test.ts`:
- Around line 121-126: Import afterEach from vitest and register
vi.unstubAllGlobals() in an afterEach teardown for SettingsPage tests. Remove
the inline teardown call from the test containing the confirm stub, while
preserving the existing assertions and stub setup.

In `@tests/unit/test_system_domain.py`:
- Around line 811-833: Update
test_update_providers_accepts_safe_objects_and_preserves_blank_existing_key so
the mocked configuration reflects the captured EXTRA_NEWZNABS transaction before
asserting result["providers"][0]["api_key_set"], or remove that assertion. Add a
test case with a provider row lacking id and a stored host containing userinfo
to exercise the by_identity fallback in update_providers.
🪄 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: c2d9a51b-a001-4a8e-b609-1e0e86f70f5c

📥 Commits

Reviewing files that changed from the base of the PR and between debef1f and 2b398f6.

⛔ Files ignored due to path filters (1)
  • frontend/src/types/config.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (25)
  • comicarr/app/config/registry.py
  • comicarr/app/search/health.py
  • comicarr/app/system/router.py
  • comicarr/app/system/service.py
  • comicarr/config.py
  • comicarr/sabnzbd.py
  • comicarr/search.py
  • config.ini.sample
  • frontend/src/components/settings/AcquisitionHealthTab.tsx
  • frontend/src/components/settings/DownloadClientsTab.tsx
  • frontend/src/components/settings/SearchTab.tsx
  • frontend/src/hooks/useConfig.ts
  • frontend/src/lib/configSave.ts
  • frontend/src/lib/healthBand.ts
  • frontend/src/pages/SeriesDetailPage.tsx
  • frontend/src/pages/SettingsPage.tsx
  • frontend/src/types/config.ts
  • frontend/src/types/index.ts
  • frontend/tests/components/AcquisitionHealthTab.test.tsx
  • frontend/tests/lib/healthBand.test.ts
  • frontend/tests/pages/SeriesDetailPage.test.tsx
  • frontend/tests/pages/SettingsPage.test.ts
  • tests/unit/test_sab_addfile_handoff.py
  • tests/unit/test_search_health.py
  • tests/unit/test_system_domain.py

Comment thread comicarr/app/system/service.py
Comment thread comicarr/search.py
Comment thread frontend/src/components/settings/SearchTab.tsx
Comment thread frontend/src/pages/SettingsPage.tsx Outdated
@frankieramirez
frankieramirez merged commit 267adbf into main Aug 9, 2026
31 checks passed
@frankieramirez
frankieramirez deleted the frankieramirez/investigate-github-issue-589 branch August 9, 2026 23:26
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.

Search and download not possible - hint needed

1 participant