Skip to content

fix(dashboard): limit automatic provider health checks - #363

Merged
njbrake merged 2 commits into
mainfrom
fix/provider-status-cache
Jul 22, 2026
Merged

fix(dashboard): limit automatic provider health checks#363
njbrake merged 2 commits into
mainfrom
fix/provider-status-cache

Conversation

@njbrake

@njbrake njbrake commented Jul 22, 2026

Copy link
Copy Markdown
Member

Description

Limits automatic dashboard provider-health checks to once per hour, preventing the Providers page and Overview from repeatedly fanning out model-list requests to every configured provider. The manual Re-check all action still forces an immediate live check.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #358

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran the Definition of Done checks locally (make lint, make typecheck, make test).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py).

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used:

Codex

Any additional AI details you'd like to share:

Implemented the focused dashboard caching change, added a Vitest regression test, rebuilt the committed dashboard bundle, and ran the local checks listed above.

NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)

  • I am an AI Agent filling out this form (check box if true)

Summary

  • Limited automatic provider-health checks to once per hour, reducing repeated requests when loading the Providers and Overview pages.
  • Preserved the manual “Re-check all” action for immediate checks.
  • Added coverage confirming cached health data prevents unnecessary rechecks.
  • Rebuilt the committed dashboard assets and updated the entrypoint reference.

Validation

  • Lint, typecheck, and test checks completed.

Provider health fans out to every configured provider, so keep automatic checks to an hourly cadence while preserving the explicit forced re-check action.

Fixes #358
@njbrake
njbrake temporarily deployed to integration-tests July 22, 2026 12:59 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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

Review profile: CHILL

Plan: Pro

Run ID: 0e40a7fe-5b0c-48fc-a04e-527e4da9f3b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4a087e9 and 3849610.

📒 Files selected for processing (2)
  • web/src/api/hooks.ts
  • web/src/pages/ProvidersPage.test.tsx

Walkthrough

The dashboard provider-health hook now refreshes hourly, pages use the hook’s fixed policy, tests verify cached health is not refetched within an hour, and the HTML entrypoint references a rebuilt React/Router bundle.

Changes

Provider health refresh

Layer / File(s) Summary
Hourly provider health refresh
web/src/api/hooks.ts, web/src/pages/OverviewPage.tsx
Provider health uses hourly stale and polling intervals, while OverviewPage relies on the hook’s default behavior.
Cached health refresh test
web/src/pages/ProvidersPage.test.tsx
Tests reuse a QueryClient, count health requests, and verify cached data prevents another request within an hour.
Dashboard runtime asset wiring
src/gateway/static/dashboard/index.html, src/gateway/static/dashboard/assets/index-DnD03gwo.js
The entrypoint references the rebuilt hashed bundle containing React, ReactDOM, and React Router runtimes.

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

Possibly related PRs

Suggested reviewers: khaledosman

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title matches the change, but it does not use the required Conventional Commit prefix format. Rewrite the title to start with a valid prefix such as fix: and keep it imperative, e.g. fix: limit automatic provider health checks.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is mostly complete and follows the template with purpose, type, issues, checklist, and AI usage filled in.
Linked Issues check ✅ Passed The PR satisfies #358 by capping automatic provider-health checks to hourly refreshes while preserving manual refresh behavior.
Out of Scope Changes check ✅ Passed The changes appear scoped to the provider-health refresh fix and the required bundle/hash updates, with no clear unrelated edits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/provider-status-cache
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/provider-status-cache

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.

Copilot AI 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.

Pull request overview

This PR reduces unnecessary provider fan-out from the dashboard by throttling automatic provider health checks to a once-per-hour cadence, while keeping the manual “Re-check all” action as an immediate live refresh.

Changes:

  • Updated useProviderHealth to cache results for 1 hour and automatically refetch at most hourly.
  • Removed the Overview page’s 60s health polling, using the hook’s new default behavior instead.
  • Added a regression test ensuring cached provider health prevents repeated automatic checks within an hour, and updated the committed dashboard entrypoint asset hash.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

File Description
web/src/pages/ProvidersPage.test.tsx Adds a Vitest regression test to confirm provider health isn’t automatically re-fetched within an hour when cached.
web/src/pages/OverviewPage.tsx Stops passing a 60s polling interval; relies on the hook’s new throttled behavior.
web/src/api/hooks.ts Introduces a 1-hour refresh interval for provider health (staleTime + refetchInterval) and simplifies the hook signature.
src/gateway/static/dashboard/index.html Updates the hashed dashboard JS entrypoint reference to match the rebuilt bundle.

@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

🧹 Nitpick comments (2)
web/src/pages/ProvidersPage.test.tsx (2)

396-412: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test the one-hour boundary explicitly.

A 31-second-old cache would also pass with any policy longer than 31 seconds, so this test does not protect the stated hourly behavior. Seed data just under the shared hourly interval, then advance past it with fake timers and assert the automatic refresh.

🤖 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 `@web/src/pages/ProvidersPage.test.tsx` around lines 396 - 412, Update the test
around the “does not automatically re-check all providers within an hour” case
to seed provider-health data just under the shared one-hour interval, using fake
timers. Advance the timer beyond that interval, remount or trigger the query
lifecycle, and assert that healthRequestCount increases, while preserving the
existing no-refresh assertion before the boundary.

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

Reuse the provider-health query-key constant.

The test duplicates ["provider-health"] from web/src/api/hooks.ts. Export and reuse a shared query-key constant in the hook, mutation, and test so cache seeding cannot drift from the production key.

As per coding guidelines, keep query keys as module constants.

🤖 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 `@web/src/pages/ProvidersPage.test.tsx` at line 405, Define and export a
module-level provider-health query-key constant in the API hooks module, then
update the provider-health hook, mutation, and ProvidersPage test to reuse it
instead of inline ["provider-health"] arrays. Keep the query key centralized and
preserve existing cache behavior.

Source: Coding guidelines

🤖 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 `@web/src/api/hooks.ts`:
- Around line 139-144: Update useProviderHealth to set gcTime to
PROVIDER_HEALTH_REFRESH_MS alongside staleTime, keeping provider-health data
cached for the full hourly interval. Add or update the remount test for
useProviderHealth to verify an inactive query remains cached and does not
trigger another provider-health probe within that interval.

---

Nitpick comments:
In `@web/src/pages/ProvidersPage.test.tsx`:
- Around line 396-412: Update the test around the “does not automatically
re-check all providers within an hour” case to seed provider-health data just
under the shared one-hour interval, using fake timers. Advance the timer beyond
that interval, remount or trigger the query lifecycle, and assert that
healthRequestCount increases, while preserving the existing no-refresh assertion
before the boundary.
- Line 405: Define and export a module-level provider-health query-key constant
in the API hooks module, then update the provider-health hook, mutation, and
ProvidersPage test to reuse it instead of inline ["provider-health"] arrays.
Keep the query key centralized and preserve existing cache behavior.
🪄 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: 496d8b98-8612-46b9-a6ea-c471758ce377

📥 Commits

Reviewing files that changed from the base of the PR and between 3f31c9c and 4a087e9.

📒 Files selected for processing (5)
  • src/gateway/static/dashboard/assets/index-DnD03gwo.js
  • src/gateway/static/dashboard/index.html
  • web/src/api/hooks.ts
  • web/src/pages/OverviewPage.tsx
  • web/src/pages/ProvidersPage.test.tsx

Comment thread web/src/api/hooks.ts
Comment on lines +139 to +144
export function useProviderHealth() {
return useQuery({
queryKey: [PROVIDER_HEALTH],
queryFn: () => apiFetch<ProviderHealthResponse>("/v1/providers/health"),
// A provider's reachability does not move second to second; keep it briefly
// fresh so navigating back does not re-dial every provider.
staleTime: 30_000,
refetchInterval,
staleTime: PROVIDER_HEALTH_REFRESH_MS,
refetchInterval: PROVIDER_HEALTH_REFRESH_MS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C 3 'new QueryClient|gcTime|cacheTime' web package.json

Repository: mozilla-ai/otari

Length of output: 8683


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== web/src/provider.tsx =="
cat -n web/src/provider.tsx | sed -n '1,120p'

echo
echo "== web/src/api/hooks.ts (health query area) =="
cat -n web/src/api/hooks.ts | sed -n '120,170p'

echo
echo "== ProvidersPage test around hourly re-check =="
cat -n web/src/pages/ProvidersPage.test.tsx | sed -n '360,460p'

echo
echo "== TanStack Query dependency version =="
rg -n '"(`@tanstack/react-query`|`@tanstack/query-core`)"' web/package.json web/pnpm-lock.yaml web/package-lock.json web/yarn.lock 2>/dev/null || true

Repository: mozilla-ai/otari

Length of output: 8417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== web/src/provider.tsx =="
sed -n '1,80p' web/src/provider.tsx

echo
echo "== web/src/pages/ProvidersPage.test.tsx around the hourly re-check test =="
sed -n '380,450p' web/src/pages/ProvidersPage.test.tsx

echo
echo "== any direct use of useProviderHealth =="
rg -n "useProviderHealth|PROVIDER_HEALTH_REFRESH_MS" web/src -g '!**/*.map'

Repository: mozilla-ai/otari

Length of output: 4605


Keep provider-health cached for the full hour in web/src/api/hooks.ts.

staleTime only controls freshness; inactive queries can still be garbage-collected before the hourly window ends, so a remount can trigger another all-provider probe. Add gcTime: PROVIDER_HEALTH_REFRESH_MS and a remount test.

🤖 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 `@web/src/api/hooks.ts` around lines 139 - 144, Update useProviderHealth to set
gcTime to PROVIDER_HEALTH_REFRESH_MS alongside staleTime, keeping
provider-health data cached for the full hourly interval. Add or update the
remount test for useProviderHealth to verify an inactive query remains cached
and does not trigger another provider-health probe within that interval.

@khaledosman khaledosman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the change against CONTRIBUTING.md and the dashboard frontend standards. LGTM — correct, clean, and well-scoped.

Traced the concerns and they hold:

  • Manual re-check preserveduseRecheckProviderHealth still hits ?refresh=true + setQueryData.
  • No freshness regression on credential changesinvalidateProviderViews invalidates PROVIDER_HEALTH, and invalidateQueries forces an immediate refetch on active queries regardless of staleTime, so add/edit/delete still re-checks right away. The 1h staleTime only suppresses passive navigation refetches, which is the intended fix for #358.
  • No focus-refetch interaction — global default is refetchOnWindowFocus: false.
  • Both callers handled — Overview dropped its arg; Providers gains a harmless hourly background poll. No other callers.

One optional test nit inline; not blocking.

Review created by Claude Code.

Comment thread web/src/pages/ProvidersPage.test.tsx Outdated

first.unmount();
client.setQueryData(["provider-health"], healthResponse([{ instance: "openai", ok: true, model_count: 3, error: null, checked_at: null }]), {
updatedAt: Date.now() - 31_000,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional: 31_000 (31s) encodes the previous 30s staleTime as a magic number — now that the threshold is 1h it reads as arbitrary. Also, await waitFor(() => expect(healthRequestCount(...)).toBe(1)) resolves on the first passing tick, so it is a weak guard against a delayed refetch. Consider a comment tying the value to "older than any plausible fresh window" and/or asserting the count holds after a short flush. Not blocking.

Comment created by Claude Code.

@njbrake
njbrake temporarily deployed to integration-tests July 22, 2026 13:36 — with GitHub Actions Inactive
@njbrake
njbrake enabled auto-merge (squash) July 22, 2026 13:40
@njbrake
njbrake merged commit e114665 into main Jul 22, 2026
7 checks passed
@njbrake
njbrake deleted the fix/provider-status-cache branch July 22, 2026 13:40
@njbrake njbrake mentioned this pull request Jul 22, 2026
4 tasks
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.

Dashboard: don't check all provider statuses every time you load the provider page

3 participants