From c64f6429ecc81caa5e4e28eee16de0789c6a887c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20L=C3=B6ffler?= Date: Fri, 11 Sep 2026 09:24:09 +0200 Subject: [PATCH] fix(terraform): omit empty key_alias from /key/update payload /key/generate leaves key_alias out when it is unset, but UpdateKey built its payload by hand and always sent it, so updating a key that has no alias wrote key_alias: "" to the database. The proxy then treats "" as a real alias and _enforce_unique_key_alias rejects the next aliasless key's update with a 400 saying the alias already exists. Send key_alias only when it is set, matching /key/generate. --- terraform/provider/litellm/client.go | 8 ++++- .../provider/litellm/resource_key_test.go | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/terraform/provider/litellm/client.go b/terraform/provider/litellm/client.go index e68b8a3a80b..566a8b107ff 100644 --- a/terraform/provider/litellm/client.go +++ b/terraform/provider/litellm/client.go @@ -126,13 +126,19 @@ func (c *Client) UpdateKey(key *Key) (*Key, error) { updateData := map[string]interface{}{ "key": key.Key, "team_id": key.TeamID, - "key_alias": key.KeyAlias, "aliases": key.Aliases, "permissions": key.Permissions, "model_max_budget": key.ModelMaxBudget, "blocked": key.Blocked, } + // /key/generate omits an empty key_alias, so sending "" here would store an + // alias the key never had and collide with every other aliasless key on the + // proxy's uniqueness check. + if key.KeyAlias != "" { + updateData["key_alias"] = key.KeyAlias + } + // The proxy keeps the stored metadata only when the field is absent, so nil means omit. if key.Metadata != nil { updateData["metadata"] = key.Metadata diff --git a/terraform/provider/litellm/resource_key_test.go b/terraform/provider/litellm/resource_key_test.go index 66291eadcc5..acf6c02e2a6 100644 --- a/terraform/provider/litellm/resource_key_test.go +++ b/terraform/provider/litellm/resource_key_test.go @@ -319,6 +319,35 @@ func TestUpdateKeyOmitsEmptyBudgetDuration(t *testing.T) { } } +// /key/generate omits an empty key_alias, so an update that sends "" stores an +// alias the key never had, and the proxy then 400s every other aliasless key on +// its unique-alias check. +func TestUpdateKeyOmitsEmptyKeyAlias(t *testing.T) { + var captured map[string]interface{} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + json.Unmarshal(body, &captured) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"key": "sk-test"}`)) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + if _, err := client.UpdateKey(&Key{Key: "sk-test"}); err != nil { + t.Fatalf("UpdateKey returned error: %v", err) + } + if _, present := captured["key_alias"]; present { + t.Errorf("update payload contains empty key_alias: %v", captured["key_alias"]) + } + + if _, err := client.UpdateKey(&Key{Key: "sk-test", KeyAlias: "alias-1"}); err != nil { + t.Fatalf("UpdateKey returned error: %v", err) + } + if captured["key_alias"] != "alias-1" { + t.Errorf("key_alias = %v, want alias-1", captured["key_alias"]) + } +} + func TestResourceKeyUpdateFailureKeepsPriorState(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")