Skip to content

fix(dashboard): hide Rate Limits nav item when the feature is disabled#512

Merged
SantiagoDePolonia merged 2 commits into
mainfrom
fix/rate-disabled
Jul 8, 2026
Merged

fix(dashboard): hide Rate Limits nav item when the feature is disabled#512
SantiagoDePolonia merged 2 commits into
mainfrom
fix/rate-disabled

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

With RATE_LIMITS_ENABLED=false, the Rate Limits sidebar item stayed visible, and the dashboard kept calling GET /admin/rate-limits — which returns 503 feature_unavailable — logging a server-side error on every models-page load.

Root cause

All the gating was already in place: sidebar.html has x-show="rateLimitsEnabled()", and the backend correctly serves "RATE_LIMITS_ENABLED": "off".

The flag never reached the gate. workflowRuntimeConfigKeys() in workflows.js is a client-side allowlist that filters the /admin/runtime/config payload, and RATE_LIMITS_ENABLED was missing from it. The key was dropped, workflowRuntimeBooleanFlag saw an empty value, and fell back to its true default. BUDGETS_ENABLED and GUARDRAILS_ENABLED are allowlisted, which is why only rate limits misbehaved.

Second defect, found while verifying

Fixing the allowlist hid the nav item, but the dashboard still issued the doomed request. dashboardDataFetches() starts fetchWorkflowsPage() (which loads the flags) and fetchRateLimitsPage() in the same eager batch, so the gate read an empty config and defaulted to true. A race, not a logic error. fetchBudgetsPage() had the identical latent defect.

Fixed at the source: fetchWorkflowRuntimeConfig() now shares its in-flight request, and a new ensureWorkflowRuntimeConfig() lets a feature gate await the flags instead of racing them. Both gates await it. Side effect: collapses the duplicate /admin/runtime/config GET that overlapping callers used to issue.

Guard against recurrence

The JS allowlist silently duplicates DashboardConfigResponse's json tags — that drift is this bug. internal/admin/dashboard_config_contract_test.go reflects over the struct, parses the allowlist out of workflows.js, and fails in both directions (backend emits a key the JS drops; JS allowlists a key the backend never sends).

Verification

Driven against a real running gateway in headless Chrome (CDP), reading the actual DOM:

Sidebar item /admin/rate-limits calls 503s
RATE_LIMITS_ENABLED=false hidden (display: none) 0 0
RATE_LIMITS_ENABLED=true visible 1 0

With the feature off, also confirmed: the deep-linked page shows "Rate limit management is unavailable.", the Create button and models-page gauge buttons are hidden, and /admin/runtime/config is fetched exactly once.

Each new test was confirmed non-vacuous by reverting the corresponding fix and watching it fail with the right diagnosis.

go build ./..., go vet, make test-race, make lint, and 446 dashboard JS tests pass.

User-visible impact

With RATE_LIMITS_ENABLED=false, the Rate Limits nav item is hidden and the dashboard no longer calls the disabled endpoint. No behavior change when the feature is enabled. No config, API, or provider behavior changes.

Notes on scope

Two things deliberately left alone:

  • Deep-linking to /admin/dashboard/rate-limits still renders the page shell with an "unavailable" warning rather than redirecting — this matches Budgets and Guardrails, so it stays consistent.
  • Feature gates fail open (default true until the config lands), so there is a sub-100ms window on first paint where the item can flash before hiding. That is the pre-existing trade-off shared with the other two flags: a config-fetch failure shows working features rather than hiding them. Changing it would be a broader UX decision than this fix.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for the rate-limits feature flag in the dashboard runtime configuration flow.
    • Runtime settings now ensure required flags are loaded before budgets and rate-limits pages determine availability.
  • Bug Fixes
    • Prevented unnecessary network requests when budgets or rate limits are disabled.
    • Improved handling of overlapping runtime-configuration loads by reusing the same in-flight fetch.
  • Tests
    • Added/updated async tests for disabled-state behavior, runtime-flag load coalescing, and retry handling.
    • Strengthened backend/frontend allowlist contract coverage to include the rate-limits flag.

The sidebar item, the Rate Limits page affordances, and the models-page
gauge buttons were already gated on `rateLimitsEnabled()`, and the backend
already served `RATE_LIMITS_ENABLED: "off"`. The flag never reached the gate:
`workflowRuntimeConfigKeys()` is a client-side allowlist that filters the
`/admin/runtime/config` payload, and `RATE_LIMITS_ENABLED` was missing from
it. The key was dropped, `workflowRuntimeBooleanFlag` saw an empty value, and
fell back to its `true` default. `BUDGETS_ENABLED` and `GUARDRAILS_ENABLED`
are allowlisted, which is why only rate limits misbehaved.

Fixing the allowlist exposed a second defect. `dashboardDataFetches()` starts
`fetchWorkflowsPage()` (which loads the flags) and `fetchRateLimitsPage()` in
the same eager batch, so the gate still read an empty config, defaulted to
`true`, and issued `GET /admin/rate-limits` — a 503 logged as an error on
every models-page load with the feature off. `fetchBudgetsPage()` had the
same latent race.

`fetchWorkflowRuntimeConfig()` now shares its in-flight request, and a new
`ensureWorkflowRuntimeConfig()` lets a feature gate await the flags instead
of racing them. Both gates await it. This also collapses the duplicate
`/admin/runtime/config` GET that overlapping callers used to issue.

The JS allowlist silently duplicates `DashboardConfigResponse`'s json tags,
which is precisely the drift that caused this. A contract test now reflects
over the struct, parses the allowlist, and fails in both directions.

User-visible impact: with `RATE_LIMITS_ENABLED=false` the Rate Limits nav
item is hidden and the dashboard no longer calls the disabled endpoint.
No behavior change when the feature is enabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ec3c072-c2da-4289-91fe-4ac43c3985a6

📥 Commits

Reviewing files that changed from the base of the PR and between bdc9f6a and e62c234.

📒 Files selected for processing (2)
  • internal/admin/dashboard/static/js/modules/workflows.js
  • internal/admin/dashboard/static/js/modules/workflows.test.cjs

📝 Walkthrough

Walkthrough

This PR adds shared runtime-config promise caching in workflows.js, introduces ensureWorkflowRuntimeConfig(), and makes budgets and rate-limits wait for config loading before checking feature flags. It also adds RATE_LIMITS_ENABLED to the allowlist and verifies the backend/frontend contract.

Changes

Runtime Config Caching and Consumer Wiring

Layer / File(s) Summary
Shared runtime config fetch caching
internal/admin/dashboard/static/js/modules/workflows.js, internal/admin/dashboard/static/js/modules/workflows.test.cjs
Adds workflowRuntimeConfigLoaded/workflowRuntimeConfigPromise state, replaces fetchWorkflowRuntimeConfig with a promise-sharing wrapper, adds ensureWorkflowRuntimeConfig() and loadWorkflowRuntimeConfig(), includes RATE_LIMITS_ENABLED in the allowed keys, and adds tests for coalescing concurrent fetches and retry behavior.
Budgets page awaits runtime config
internal/admin/dashboard/static/js/modules/budgets.js, internal/admin/dashboard/static/js/modules/budgets.test.cjs
fetchBudgetsPage awaits ensureWorkflowRuntimeConfig() before checking budgetManagementEnabled(); new test verifies no fetch occurs when disabled.
Rate-limits page awaits runtime config
internal/admin/dashboard/static/js/modules/rate-limits.js, internal/admin/dashboard/static/js/modules/rate-limits.test.cjs
fetchRateLimitsPage() awaits ensureWorkflowRuntimeConfig() before checking rateLimitsEnabled(); new test verifies no fetch occurs when disabled.
Backend/frontend allowlist contract test
internal/admin/dashboard_config_contract_test.go, internal/admin/handler_test.go
New Go test reflects over DashboardConfigResponse JSON tags and compares them against the frontend allowlist parsed from workflows.js; existing handler test extended to cover RateLimitsEnabled.

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

Sequence Diagram(s)

sequenceDiagram
  participant BudgetsPage as budgets.js
  participant RateLimitsPage as rate-limits.js
  participant Workflows as workflows.js
  participant API as /admin/runtime/config

  BudgetsPage->>Workflows: ensureWorkflowRuntimeConfig()
  RateLimitsPage->>Workflows: ensureWorkflowRuntimeConfig()
  Workflows->>API: GET runtime config
  API-->>Workflows: config JSON incl. RATE_LIMITS_ENABLED
  Workflows-->>BudgetsPage: config loaded
  Workflows-->>RateLimitsPage: config loaded
  BudgetsPage->>BudgetsPage: check budgetManagementEnabled()
  RateLimitsPage->>RateLimitsPage: check rateLimitsEnabled()
Loading

Possibly related PRs

  • ENTERPILOT/GoModel#186: Extends the same dashboard runtime config allowlist plumbing consumed by workflows.js for UI flag gating.
  • ENTERPILOT/GoModel#482: Touches the same fetchRateLimitsPage() flow that this PR modifies to await runtime config.

Poem

A rabbit waits, no need to rush,
One fetch to serve the whole big bunch 🐇
Budgets, limits, side by side,
Now share one config, synchronized.
Hop, hop, cached and true —
No more races, just one queue!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: hiding the Rate Limits nav item when the feature is disabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rate-disabled

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.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge with low risk.

The change is focused on dashboard runtime-config gating and preserves the existing fail-open behavior. The updated tests cover the missing allowlist key, the fetch-order race, and the backend/frontend config contract.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the general contract validation for the dashboard and verified the network trace shows exactly one GET /admin/runtime/config request with a 200 response and no /admin/rate-limits requests.
  • Compared DOM traces to verify the Rate Limits anchor is hidden in the disabled view and visible in the enabled view, with the disabled state showing display:none and zero width/height and visible:false, and the enabled state showing display:flex with non-zero dimensions and visible:true.
  • Confirmed the dashboard JS tests passed, as indicated by the dashboard-js-tests.log reporting an exit code of 0 for the changed test files.
  • Captured two screenshots showing the dashboard with RATE_LIMITS_ENABLED=false and RATE_LIMITS_ENABLED=true.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Dashboard as Dashboard fetchAll()
participant Workflows as Workflows module
participant Gate as Budget/Rate Limit gate
participant Config as /admin/runtime/config
participant Feature as /admin/budgets or /admin/rate-limits

Dashboard->>Workflows: fetchWorkflowsPage()
Workflows->>Config: fetchWorkflowRuntimeConfig()
Workflows-->>Workflows: share in-flight promise
Dashboard->>Gate: fetchBudgetsPage()/fetchRateLimitsPage()
Gate->>Workflows: ensureWorkflowRuntimeConfig()
Workflows-->>Gate: runtime flags loaded
alt feature disabled
    Gate-->>Dashboard: clear local state, skip feature endpoint
else feature enabled
    Gate->>Feature: fetch feature data
    Feature-->>Gate: data response
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Dashboard as Dashboard fetchAll()
participant Workflows as Workflows module
participant Gate as Budget/Rate Limit gate
participant Config as /admin/runtime/config
participant Feature as /admin/budgets or /admin/rate-limits

Dashboard->>Workflows: fetchWorkflowsPage()
Workflows->>Config: fetchWorkflowRuntimeConfig()
Workflows-->>Workflows: share in-flight promise
Dashboard->>Gate: fetchBudgetsPage()/fetchRateLimitsPage()
Gate->>Workflows: ensureWorkflowRuntimeConfig()
Workflows-->>Gate: runtime flags loaded
alt feature disabled
    Gate-->>Dashboard: clear local state, skip feature endpoint
else feature enabled
    Gate->>Feature: fetch feature data
    Feature-->>Gate: data response
end
Loading

Reviews (1): Last reviewed commit: "fix(dashboard): hide Rate Limits nav ite..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/admin/dashboard/static/js/modules/workflows.js (1)

759-822: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Only mark the runtime config as loaded after a successful fetch. A transient failure on the first /admin/runtime/config request sets workflowRuntimeConfigLoaded = true, so ensureWorkflowRuntimeConfig() stops retrying and feature gates stay on their default values for the rest of the session.

🤖 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 `@internal/admin/dashboard/static/js/modules/workflows.js` around lines 759 -
822, The runtime config fetch path in
fetchWorkflowRuntimeConfig/loadWorkflowRuntimeConfig is marking
workflowRuntimeConfigLoaded as true even when /admin/runtime/config fails, which
prevents ensureWorkflowRuntimeConfig from retrying. Move the loaded flag update
so it only happens after a successful fetch and response handling, while leaving
failures to keep workflowRuntimeConfigPromise cleared and allow later retries.
Use the existing symbols fetchWorkflowRuntimeConfig,
ensureWorkflowRuntimeConfig, and loadWorkflowRuntimeConfig to update the
success/failure flow without changing the caller contract.
🤖 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.

Outside diff comments:
In `@internal/admin/dashboard/static/js/modules/workflows.js`:
- Around line 759-822: The runtime config fetch path in
fetchWorkflowRuntimeConfig/loadWorkflowRuntimeConfig is marking
workflowRuntimeConfigLoaded as true even when /admin/runtime/config fails, which
prevents ensureWorkflowRuntimeConfig from retrying. Move the loaded flag update
so it only happens after a successful fetch and response handling, while leaving
failures to keep workflowRuntimeConfigPromise cleared and allow later retries.
Use the existing symbols fetchWorkflowRuntimeConfig,
ensureWorkflowRuntimeConfig, and loadWorkflowRuntimeConfig to update the
success/failure flow without changing the caller contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d0acadc3-ac60-4a19-b2cb-1d17fd9a7fc4

📥 Commits

Reviewing files that changed from the base of the PR and between e0eb0ee and bdc9f6a.

📒 Files selected for processing (8)
  • internal/admin/dashboard/static/js/modules/budgets.js
  • internal/admin/dashboard/static/js/modules/budgets.test.cjs
  • internal/admin/dashboard/static/js/modules/rate-limits.js
  • internal/admin/dashboard/static/js/modules/rate-limits.test.cjs
  • internal/admin/dashboard/static/js/modules/workflows.js
  • internal/admin/dashboard/static/js/modules/workflows.test.cjs
  • internal/admin/dashboard_config_contract_test.go
  • internal/admin/handler_test.go

`fetchWorkflowRuntimeConfig()` marked the flags loaded from a `.finally()`,
so a failed `/admin/runtime/config` fetch counted as a successful load.
`loadWorkflowRuntimeConfig()` swallows its own errors and resolves normally
on a network throw, on an unhandled response (auth failure, non-2xx), and on
stale auth, so every failure path set `workflowRuntimeConfigLoaded = true`
alongside an empty config.

`ensureWorkflowRuntimeConfig()` then short-circuited forever: a single failed
load left every feature gate reading an empty config and falling back to its
default. `_applyRoute()` calls `fetchRateLimitsPage()` on its own when
navigating, with no config fetch alongside, so the Rate Limits nav item
reappeared and the dashboard called `/admin/rate-limits` — a 503 — even with
`RATE_LIMITS_ENABLED=false`.

The flag now tracks whether `workflowRuntimeConfig` holds a real response: set
on success, cleared wherever the config is wiped. `.finally()` keeps clearing
the in-flight promise, so failures stay retryable and the caller contract is
unchanged. The stale-auth path deliberately leaves the flag untouched, since a
superseded request must not invalidate a good prior load.

Verified end-to-end against a running gateway: booting with a bad API key then
supplying the correct one previously left `loaded=true` with no retry and one
503; it now retries, reads `RATE_LIMITS_ENABLED=off`, and issues no request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SantiagoDePolonia SantiagoDePolonia merged commit 23e3113 into main Jul 8, 2026
20 checks passed
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.

2 participants