Skip to content

fix(search): backend rate-limit no longer cascades into GitHub-direct storm + global dialog#524

Merged
rainxchzed merged 3 commits into
mainfrom
fix/search-rate-limit-cascade
May 6, 2026
Merged

fix(search): backend rate-limit no longer cascades into GitHub-direct storm + global dialog#524
rainxchzed merged 3 commits into
mainfrom
fix/search-rate-limit-cascade

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 6, 2026

Why

Logged-in user reported the global "rate limit exceeded" dialog firing on Search even with auth.

Root cause cascade

  1. `tryBackendSearch` swallows the exception type via `result.getOrNull()`. Backend 429 (`RateLimitedException`) → returns null → triggers `fallbackGithubSearch`.
  2. Fallback hits direct GitHub `/search/repositories` AND `verifyBatch` fires up to 100 concurrent `/repos/{o}/{n}/releases` calls per page (semaphore=15).
  3. GitHub abuse detector / search-specific rate-limit (30/min) trips even for logged-in users → 429.
  4. `RateLimitInterceptor` (direct-GitHub only path per Fix rate-limit dialog firing on 200 responses + skip Details enrichment when anon #508) sees the 429 → fires the global dialog.

End state: backend says "I'm busy" → client tries to compensate by hammering GitHub → both buckets exhausted → noisy dialog instead of soft retry-after toast.

Fix

  • `tryBackendSearch` now uses `shouldFallbackToGithubOrRethrow` (same policy `DetailsRepositoryImpl` uses). Backend 429 throws domain `RateLimitException` → never falls back to GitHub direct. 5xx / network errors still fall through (legitimate fallback case).
  • `SearchViewModel` formats the rate-limit error via `Res.string.rate_limit_exceeded_retry_in` (countdown) / `rate_limit_exceeded` (no countdown), matching the friendly message style `DetailsViewModel` already uses (Rate-limit UX polish (#506, stacks on #505) #506).

Test plan

  • Mock backend search to return 429 → no global dialog, no GitHub-direct calls in network log; UI shows "Rate limit hit — retry in Ns" message.
  • Mock backend search to return 5xx → fallback to GitHub direct still works (regression check).
  • Backend search succeeds normally → no behavior change.
  • Picking MostForks (still skips backend by design) → fallback still works.
  • What's-new sheet on next 1.8.1 install shows the fix bullet in device language.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed the Add-by-link button on the Apps screen covering the last app in the list.
    • Improved search error handling: when the backend is busy, a friendly "try again later" toast is shown instead of a rate-limit/error dialog.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a51deed2-0770-43b2-8bb8-d09aa5baa7e7

📥 Commits

Reviewing files that changed from the base of the PR and between 15f4415 and acea291.

📒 Files selected for processing (1)
  • feature/search/data/src/commonMain/kotlin/zed/rainxch/search/data/repository/SearchRepositoryImpl.kt

Walkthrough

This PR adds translated release-note bullets in 13 languages and updates search error handling: backend failures now decide between rethrow vs. GitHub fallback, and RateLimitException produces localized retry-aware messages surfaced to the UI.

Changes

Search Error Handling & Release Notes

Layer / File(s) Summary
Release Notes (data)
core/presentation/.../whatsnew/{16,ar,bn,es,fr,hi,it,ja,ko,pl,ru,tr,zh-CN}/16.json
Added a second FIXED bullet in each locale: preserves the Apps-screen add-by-link fix and adds that search shows a retry toast (instead of a GitHub rate-limit dialog) when the backend is busy.
Repository Error Handling (core logic)
feature/search/data/.../SearchRepositoryImpl.kt
Refactored backend search handling to Result.fold(onSuccess,onFailure); onFailure consults shouldFallbackToGithubOrRethrow(e) to decide whether to rethrow or return null to trigger GitHub REST fallback. New import added.
ViewModel / Presentation
feature/search/presentation/.../SearchViewModel.kt
RateLimitException handling updated to compute localized messages using remaining-reset seconds (choosing between immediate limit vs. retry-after string) and adjust loading/error state accordingly.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as Client(UI)
    participant VM as SearchViewModel
    participant Repo as SearchRepository
    participant Backend as Backend Search Service
    participant GH as GitHub REST

    UI->>VM: performSearch(query)
    VM->>Repo: backendSearch(query)
    Repo->>Backend: request
    alt Backend success
        Backend-->>Repo: results
        Repo-->>VM: mapped PaginatedDiscoveryRepositories
        VM-->>UI: display results
    else Backend failure
        Backend-->>Repo: error (e)
        Repo->>Repo: shouldFallbackToGithubOrRethrow(e)?
        alt Rethrow
            Repo-->>VM: throw e
            VM->>UI: show error (RateLimitException handled specially)
        else Fallback
            Repo-->>GH: fallbackSearch(query)
            GH-->>Repo: results
            Repo-->>VM: mapped GitHub results
            VM-->>UI: display results
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰
I nibble on bugs and patch them neat,
When backends nap, I make retries sweet.
No scary dialogs, just a friendly toast—
Hop, retry, and find what you love most. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 pull request title clearly and specifically describes the main fix: preventing backend rate-limit exceptions from cascading into excessive direct GitHub API calls that trigger a global dialog.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/search-rate-limit-cascade

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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
`@feature/search/data/src/commonMain/kotlin/zed/rainxch/search/data/repository/SearchRepositoryImpl.kt`:
- Around line 158-170: The onFailure lambda in SearchRepositoryImpl's
tryBackendSearch is currently returning null in both branches so non-fallback
errors silently trigger the GitHub REST fallback; change the logic so that when
shouldFallbackToGithubOrRethrow(e) returns false you rethrow the exception (or
propagate it) instead of returning null, and only return null when the function
indicates a fallback (true) so the caller falls back to GitHub; update the
onFailure branch that references shouldFallbackToGithubOrRethrow to throw e (or
rethrow) and keep the null return only for the fallback path.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 957bb937-48e6-4e2c-bf4f-ee620e72901e

📥 Commits

Reviewing files that changed from the base of the PR and between 868388c and 15f4415.

📒 Files selected for processing (15)
  • core/presentation/src/commonMain/composeResources/files/whatsnew/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/16.json
  • feature/search/data/src/commonMain/kotlin/zed/rainxch/search/data/repository/SearchRepositoryImpl.kt
  • feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt

@rainxchzed rainxchzed merged commit c69e8e5 into main May 6, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/search-rate-limit-cascade branch May 6, 2026 06:04
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