fix: stop a cleared Organization field from failing key creation (#39316)

* fix: stop a cleared Organization field from failing key creation

Clearing the Organization combobox in the Create Key modal left organization_id set to an empty string, so /key/generate looked up an organization named "" and failed with "Organization doesn't exist in db. Organization=".

OrganizationDropdown now emits null on clear, and GenerateKeyRequest normalizes an empty organization_id or project_id to None the same way it already does for team_id.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test: drop customer-specific docstring from key request normalization test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yassin <yassin@berri.ai>
This commit is contained in:
devin-ai-integration[bot] 2026-09-03 19:12:11 +00:00 committed by GitHub
parent e046aee3d5
commit 92122086ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 32 additions and 9 deletions

View file

@ -1216,7 +1216,7 @@ class GenerateKeyRequest(KeyRequestBase):
organization_id: str | None = None
project_id: str | None = None
@field_validator("team_id", "organization_id", mode="before")
@field_validator("team_id", "organization_id", "project_id", mode="before")
@classmethod
def treat_cleared_id_as_unset(cls, v: object) -> object:
if v == "":

View file

@ -17505,6 +17505,18 @@ def test_generate_key_request_blank_team_id_is_personal():
assert GenerateKeyRequest(team_id="team-1").team_id == "team-1"
def test_generate_key_request_blank_organization_and_project_id_are_unset():
from litellm.proxy._types import RegenerateKeyRequest
cleared = GenerateKeyRequest(organization_id="", project_id="")
assert cleared.organization_id is None
assert cleared.project_id is None
assert "organization_id" not in cleared.model_dump(exclude_none=True)
assert RegenerateKeyRequest(organization_id="").organization_id is None
assert GenerateKeyRequest(organization_id="org-1", project_id="proj-1").organization_id == "org-1"
assert GenerateKeyRequest(organization_id="org-1", project_id="proj-1").project_id == "proj-1"
def test_key_request_blank_organization_id_is_unset():
from litellm.proxy._types import RegenerateKeyRequest, UpdateKeyRequest

View file

@ -58,6 +58,17 @@ describe("OrganizationDropdown", () => {
expect(onChange.mock.calls[0][0]).toBe("org-1");
});
it("emits null, never the empty string, when the selection is cleared", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<OrganizationDropdown organizations={MOCK_ORGS} value="org-1" onChange={onChange} />);
await user.click(screen.getByRole("button", { name: "Clear" }));
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(null);
});
it("should filter options by organization id", async () => {
const user = userEvent.setup();
render(<OrganizationDropdown organizations={MOCK_ORGS} />);

View file

@ -5,7 +5,7 @@ import { Organization } from "../networking";
interface OrganizationDropdownProps {
organizations?: Organization[] | null;
value?: string;
onChange?: (value: string) => void;
onChange?: (value: string | null) => void;
disabled?: boolean;
loading?: boolean;
style?: React.CSSProperties;
@ -32,7 +32,7 @@ const OrganizationDropdown: React.FC<OrganizationDropdownProps> = ({
sublabel: org.organization_id,
}))}
value={value}
onValueChange={(organizationId) => onChange?.(organizationId)}
onValueChange={(organizationId) => onChange?.(organizationId || null)}
placeholder={placeholder}
emptyText={loading ? "Loading organizations…" : "No organizations found"}
disabled={disabled}

View file

@ -587,9 +587,9 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
}
};
const changeOrganization = (write: FieldWrite) => (orgId: string) => {
write(orgId || undefined);
setSelectedOrganizationId(orgId || null);
const changeOrganization = (write: FieldWrite) => (orgId: string | null) => {
write(orgId ?? undefined);
setSelectedOrganizationId(orgId);
// Clear team and project when org changes
setSelectedCreateKeyTeam(null);
setSelectedProjectId(null);

View file

@ -303,9 +303,9 @@ export function KeyEditView({
}
};
const handleOrganizationChange = (setField: (value: string | null) => void, orgId: string | undefined) => {
setField(orgId || null);
setSelectedOrganizationId(orgId || null);
const handleOrganizationChange = (setField: (value: string | null) => void, orgId: string | null) => {
setField(orgId);
setSelectedOrganizationId(orgId);
form.setValue("team_id", undefined);
};