Skip to content

fix(ui): repoint the key detail URL to the rotated hash after regenerating - #37968

Merged
yuneng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_key_regen_url_stale_hash
Aug 24, 2026
Merged

fix(ui): repoint the key detail URL to the rotated hash after regenerating#37968
yuneng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_key_regen_url_stale_hash

Conversation

@yuneng-berri

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Regenerating a key breaks that key's detail page URL
  • The page then reads "Key not found" on every reload

How it solves it:

  • Read the rotated hash from the field the API sends
  • Point the page's ?key= at it on close

User Flow

Before: an admin who regenerates a key from that key's detail page is dropped on a dead URL and loses the page

  1. They open https://litellm-domain/ui/api-keys/?key=<the key's hash> and see the key's detail page
  2. They click "Regenerate Key", then "Regenerate" in the dialog, and the new secret is shown once
  3. They click "Close"
  4. The address bar still carries the hash that was just rotated away, and the page shows "Key not found"
  5. Reloading keeps showing "Key not found", so the only way back is the keys list

After: the same admin stays on a working detail page for the key they just regenerated

  1. They open the same https://litellm-domain/ui/api-keys/?key=<the key's hash>
  2. They click "Regenerate Key", then "Regenerate" in the dialog, and the new secret is shown once
  3. They click "Close"
  4. The address bar now carries the hash the regeneration produced, and the detail page keeps rendering the key
  5. Reloading loads the same key again, and the URL is safe to bookmark or hand to another admin

The secret stays on screen until the admin dismisses the dialog, so the URL swap never cuts short their chance to copy it

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Shared setup, against a proxy on :4000 with an admin key, to create the key each case walks through:

curl -sS -X POST http://localhost:4000/key/generate \
  -H "Authorization: Bearer sk-1234" -H 'Content-Type: application/json' \
  -d '{"key_alias":"regen-url-demo"}' | jq -r '.token'

Both cases are also driven as Playwright runs against a real dashboard talking to a real proxy, no mocks and no stubbed responses. The runs cover four checks: the two cases below, plus two adjacent behaviours that must not change

Before (490c9f9)

Regenerating repoints the detail page URL

  1. Open http://localhost:4000/ui/api-keys/?key=&lt;the hash printed above>
  2. Click "Regenerate Key", then "Regenerate" in the dialog, then "Close"
  3. The address bar still carries the pre-regeneration hash, and the page reads "Key not found"

The detail page survives a reload after regenerating

  1. Repeat the steps above, then reload the page
  2. The reload re-requests the stale hash, so the page still reads "Key not found"

Both cases as a browser run

✓  1 adj-1.spec.ts:14:5 › gate: dashboard talks to the harness proxy (811ms)
✓  2 adj-1.spec.ts:20:5 › cancelling the regenerate modal leaves the ?key= param alone (1.6s)
✓  3 adj-1.spec.ts:36:5 › leaving the key detail view clears the ?key= param (905ms)
✓  4 bug-1.spec.ts:14:5 › gate: dashboard talks to the harness proxy (521ms)
✘  5 bug-1.spec.ts:20:5 › regenerating from the key detail page repoints the ?key= param (11.3s)
✘  6 bug-1.spec.ts:49:5 › the key detail page survives a reload after regenerating (11.3s)

   Expected: "7b24767cde5ed000dcbc07c6807b6163e0f7b8f7a353928cab097c982e060f51"
   Received: "9a97b0bcb7bfde4fbd28738774d41d90ba991bcaaa60b3ea383e8a6e3b8a87b7"
   - Timeout 10000ms exceeded while waiting on the predicate
   > 68 |   await expect.poll(() => new URL(page.url()).searchParams.get("key"), ...).toBe(newHash)

After (713972f)

Regenerating repoints the detail page URL

  1. Open http://localhost:4000/ui/api-keys/?key=&lt;the hash printed above>
  2. Click "Regenerate Key", then "Regenerate" in the dialog, then "Close"
  3. The address bar now carries the rotated hash, and the detail page keeps rendering the key

The detail page survives a reload after regenerating

  1. Repeat the steps above, then reload the page
  2. The reload requests the rotated hash and the key's detail page comes back, no "Key not found"

Both cases as a browser run

✓  1 adj-1.spec.ts:14:5 › gate: dashboard talks to the harness proxy (646ms)
✓  2 adj-1.spec.ts:20:5 › cancelling the regenerate modal leaves the ?key= param alone (1.6s)
✓  3 adj-1.spec.ts:36:5 › leaving the key detail view clears the ?key= param (898ms)
✓  4 bug-1.spec.ts:14:5 › gate: dashboard talks to the harness proxy (498ms)
✓  5 bug-1.spec.ts:20:5 › regenerating from the key detail page repoints the ?key= param (1.0s)
✓  6 bug-1.spec.ts:49:5 › the key detail page survives a reload after regenerating (1.5s)

   6 passed (6.9s)

The proxy log for that run shows the dashboard asking for the rotated hash and getting it:

POST /key/192e46f8244919a6afeb32081b3c1edad6b237085c2b6003ef3eaba68cd6233d/regenerate HTTP/1.1" 200 OK
GET  /key/info?key=a41eeb7a5d2b80ac80aa70fb4b958111f9182207f09015bbb2f5a10ae2e56adb HTTP/1.1" 200 OK

Type

🐛 Bug Fix

Caveats (if any)

  • Falls back to token, so older proxies keep working
  • URL swap waits for close, keeping the secret visible

…ating

Regenerating a key from the key info page left the ?key= query param on the
old hash, so dismissing the dialog or reloading landed on a key that no longer
exists and the page rendered "Key not found".

Two defects had to line up. POST /key/{key}/regenerate returns the rotated
hash in token_id and leaves token null, but RegenerateKeyModal read
response.token || response.key_id, neither of which the endpoint populates, so
it always reported the old hash back to its parent. And KeyInfoView's
onKeyDataUpdate prop had no caller anywhere in the tree: VirtualKeysTable owns
the ?key= param and mounts the view but never passed it, so even a correct
hash went nowhere.

VirtualKeysTable now handles the update by pointing ?key= at the rotated hash
and refetching. KeyInfoView holds that callback until the regenerate dialog is
dismissed rather than firing it on the API response, because swapping the
selected key mid-dialog unmounts the view and tears down the one-time
plaintext key before the user can copy it.
@greptile-apps

greptile-apps Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR keeps regenerated key detail pages usable by propagating the rotated hash and updating the ?key= parameter after the secret dialog closes

  • Reads the rotated lookup hash from token_id, retaining compatibility with responses that use token
  • Defers the parent URL update until dismissal so the regenerated secret remains visible
  • Adds focused modal and detail-page regression coverage

Confidence Score: 5/5

The PR appears safe to merge, with the rotated identifier propagated at the intended dismissal boundary

The current regeneration endpoint returns the new lookup hash through token_id, the modal preserves the secret until close, and the parent then updates the detail URL without affecting unrelated update payloads

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx Updates the selected-key query parameter and refreshes the key list when regeneration supplies a new identifier
ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx Normalizes the regeneration response so the rotated token_id becomes the key lookup token
ui/litellm-dashboard/src/components/templates/key_info_view.tsx Holds the regenerated-key update until modal dismissal while updating the visible detail data immediately
ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx Verifies that dismissal repoints the detail URL to the rotated hash
ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx Verifies extraction of the rotated hash when the API returns it through token_id

Reviews (1): Last reviewed commit: "fix(ui): repoint the key detail URL to t..." | Re-trigger Greptile

@yuneng-berri
yuneng-berri enabled auto-merge (squash) August 23, 2026 05:37
@yuneng-berri
yuneng-berri merged commit 7113685 into litellm_internal_staging Aug 24, 2026
66 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_key_regen_url_stale_hash branch August 24, 2026 17:12
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