Conversation
Support passing duration=null on /key/update to reset a key's expiry to never expires, alongside the existing "-1" magic string (kept for backward compat). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds Key changes:
Issues found:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/key_management_endpoints.py | Two targeted fixes: prepare_key_update_data now treats duration=None the same as duration="-1" (never expires), and the upperbound comparison likewise accepts None as infinite duration. Both are minimal, correct changes backed by the new test. |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | New test_prepare_key_update_data_duration_none_never_expires test mirrors the existing -1 test and correctly validates the new None path. Test is mock-only (no real network calls), consistent with the repo's test policy for this folder. |
| ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.tsx | Added "Never Expire" checkbox (edit-mode only) with Antd Checkbox. The tooltip was unified into a single message that is now inaccurate in create mode — it says "Leave empty to keep the current expiry unchanged" but in create mode leaving the field empty means "never expire". |
| ui/litellm-dashboard/src/components/templates/key_edit_view.tsx | Initializes neverExpire state from !keyData.expires and injects duration: null on submit when checked. Minor UX gap: unchecking "Never Expire" without providing a duration silently leaves the key as never-expiring, with no validation feedback. |
Sequence Diagram
sequenceDiagram
participant UI as key_edit_view.tsx
participant API as /key/update (FastAPI)
participant FN as prepare_key_update_data()
participant DB as Database
Note over UI: User checks "Never Expire"
UI->>UI: neverExpire = true<br/>values.duration = null
UI->>API: POST /key/update {duration: null}
API->>FN: UpdateKeyRequest(duration=None)
Note over FN: model_dump(exclude_unset=True)<br/>→ {duration: None}
FN->>FN: duration is None → expires = None
FN-->>API: {expires: None, ...}
API->>DB: UPDATE key SET expires = NULL
DB-->>API: OK
API-->>UI: {key updated}
Note over UI: User leaves "-1" (legacy path)
UI->>API: POST /key/update {duration: "-1"}
API->>FN: UpdateKeyRequest(duration="-1")
FN->>FN: duration == "-1" → expires = None
FN-->>API: {expires: None, ...}
API->>DB: UPDATE key SET expires = NULL
Last reviewed commit: e468b02
| ? "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to never expire." | ||
| : "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire." | ||
| } | ||
| title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged." |
There was a problem hiding this comment.
Tooltip message is inaccurate in create mode
The tooltip was previously conditional based on isCreateMode. Now it always says "Leave empty to keep the current expiry unchanged." However, in create mode there is no "current expiry" — leaving the field empty means "never expire". This is a UX regression for the key creation flow (used in create_key_button.tsx with isCreateMode={true}).
The isCreateMode prop is still passed through to KeyLifecycleSettings by callers, so it can still be used to differentiate the tooltip message.
| title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged." | |
| title={isCreateMode | |
| ? "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to never expire." | |
| : "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged."} |
| ); | ||
| const [autoRotationEnabled, setAutoRotationEnabled] = useState<boolean>(keyData.auto_rotate || false); | ||
| const [rotationInterval, setRotationInterval] = useState<string>(keyData.rotation_interval || ""); | ||
| const [neverExpire, setNeverExpire] = useState<boolean>(!keyData.expires); |
There was a problem hiding this comment.
Unchecking "Never Expire" without a duration silently keeps key as never-expiring
neverExpire is initialized to true when keyData.expires is falsy. If a user opens the edit view for a never-expiring key, unchecks the "Never Expire" checkbox (intending to add an expiry), but then doesn't fill in a duration string and saves — the backend receives duration: "". Since "" matches neither the None / "-1" branch nor the len(duration) > 0 branch in prepare_key_update_data, expires is never updated and the key silently remains as never-expiring.
Consider adding a validation step in handleSubmit (or inside KeyLifecycleSettings when the checkbox is unchecked) to surface an error when "Never Expire" is unchecked but no duration is provided.
Summary
Failure Path (Before Fix)
To reset a key's expiry to "never expires" via
/key/update, users had to passduration: "-1"— a magic string with poor discoverability. The budget fields already usenullto mean "unlimited" (e.g.,max_budget: null), but duration had no equivalent.Fix
Added support for
duration: nullon/key/updateto reset a key's expiry to never expires. The existing"-1"magic string is preserved for backward compatibility.prepare_key_update_datanow treatsduration=Nonethe same asduration="-1"(setsexpires=None). The upperbound validation also acceptsNoneas infinite duration. Docstring updated to documentnullas preferred,"-1"as deprecated.duration: nullis sent on submit. Initialized from the key's currentexpiresvalue.Testing
test_prepare_key_update_data_duration_none_never_expiresalongside the existingtest_prepare_key_update_data_duration_never_expirestest — both pass.python3 -m pytest tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py -v -k "duration"Type
🐛 Bug Fix
✅ Test