diff --git a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
index 1237082d2c..f77cf787a3 100644
--- a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
@@ -219,6 +219,34 @@ describe("RegenerateKeyModal", () => {
expect(updateCall.key_name).toBe("sk-new-regenerated-key");
});
+ it("should pass form values to onKeyUpdate even when the API echoes back different limits", async () => {
+ // Regression: when the regenerate endpoint returns GenerateKeyResponse, it echoes
+ // back the existing max_budget / tpm_limit / rpm_limit. The modal must prefer the
+ // values the user just submitted, not whatever the server echoes.
+ const user = userEvent.setup();
+ mockRegenerateKeyCall.mockResolvedValue({
+ key: "sk-new-regenerated-key",
+ token: "new-token-hash",
+ // stale values echoed from the server
+ max_budget: 9999,
+ tpm_limit: 9999,
+ rpm_limit: 9999,
+ });
+
+ renderWithProviders();
+ await user.click(screen.getByRole("button", { name: /Regenerate/ }));
+
+ await waitFor(() => {
+ expect(mockOnKeyUpdate).toHaveBeenCalledOnce();
+ });
+
+ const updateCall = mockOnKeyUpdate.mock.calls[0][0];
+ // The form's pre-filled values (from makeToken) must win over the API echo.
+ expect(updateCall.max_budget).toBe(100);
+ expect(updateCall.tpm_limit).toBe(5000);
+ expect(updateCall.rpm_limit).toBe(500);
+ });
+
it("should display key alias in success view", async () => {
const user = userEvent.setup();
mockRegenerateKeyCall.mockResolvedValue({
diff --git a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
index 3f254319bd..c714a15eb9 100644
--- a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
@@ -111,23 +111,20 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
setRegeneratedKey(response.key);
NotificationManager.success("Virtual Key regenerated successfully");
- console.log("Full regenerate response:", response); // Debug log to see what's returned
-
- // Create updated key data with ALL new values from the response
+ // Build the update payload. Spread the API response first so any new
+ // fields it returns (new token, timestamps, etc.) are captured, then
+ // override with the explicit form values — the user's just-submitted
+ // edits must win over whatever the API echoes back.
const updatedKeyData: Partial = {
- // Use the new token/key ID from the response (this is what was missing!)
- token: response.token || response.key_id || selectedToken.token, // Try different possible field names
- key_name: response.key, // This is the new secret key string
+ ...response,
+ token: response.token || response.key_id || selectedToken.token,
+ key_name: response.key,
max_budget: formValues.max_budget,
tpm_limit: formValues.tpm_limit,
rpm_limit: formValues.rpm_limit,
expires: formValues.duration ? calculateNewExpiryTime(formValues.duration) : selectedToken.expires,
- // Include any other fields that might be returned by the API
- ...response, // Spread the entire response to capture all updated fields
};
- console.log("Updated key data with new token:", updatedKeyData); // Debug log
-
// Update the parent component with new key data
if (onKeyUpdate) {
onKeyUpdate(updatedKeyData);