From e871514c1bd82395e8d2b1eb48e93710bd5ba968 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 29 Apr 2026 15:56:03 -0700 Subject: [PATCH] drop FK on owner_token to support master-key callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The proxy's master key is not stored in LiteLLM_VerificationToken, so a hard FK from LiteLLM_ScheduledTaskTable.owner_token blocked every master-key task creation with RecordNotFoundError on the nested connect. Decision: keep owner_token as a plain string column. Cleanup of orphaned rows when a key is deleted is now an operator concern (acceptable — rows are small, deletions of keys are infrequent, and the alternative locks out the most common local-dev auth path). This change: - removes the @relation declaration from all three schema.prisma files - removes the AddForeignKey statement from the migration - switches create_task back to the scalar owner_token write (no nested connect required) - documents the rationale inline on the model Co-Authored-By: Claude Opus 4.7 (1M context) --- .../20260429000000_add_scheduled_tasks/migration.sql | 7 ------- litellm-proxy-extras/litellm_proxy_extras/schema.prisma | 7 +++++-- litellm/proxy/scheduled_tasks/store.py | 4 +--- litellm/proxy/schema.prisma | 7 +++++-- schema.prisma | 7 +++++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260429000000_add_scheduled_tasks/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260429000000_add_scheduled_tasks/migration.sql index 919ae5ad431..9a564b4a05b 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260429000000_add_scheduled_tasks/migration.sql +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260429000000_add_scheduled_tasks/migration.sql @@ -40,13 +40,6 @@ CREATE INDEX "LiteLLM_ScheduledTaskTable_team_id_idx" CREATE INDEX "LiteLLM_ScheduledTaskTable_agent_id_status_idx" ON "LiteLLM_ScheduledTaskTable" ("agent_id", "status"); --- AddForeignKey -ALTER TABLE "LiteLLM_ScheduledTaskTable" - ADD CONSTRAINT "LiteLLM_ScheduledTaskTable_owner_token_fkey" - FOREIGN KEY ("owner_token") - REFERENCES "LiteLLM_VerificationToken" ("token") - ON DELETE CASCADE ON UPDATE CASCADE; - -- Hand-appended (Prisma cannot express partial indexes or CHECK constraints): -- Partial index over rows the ticker actually scans. diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index fc6cb6b04d9..24334fc04c1 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -410,7 +410,6 @@ model LiteLLM_VerificationToken { litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id]) object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) jwt_key_mappings LiteLLM_JWTKeyMapping[] - scheduled_tasks LiteLLM_ScheduledTaskTable[] // SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub" // SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2 @@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession { model LiteLLM_ScheduledTaskTable { task_id String @id @default(uuid()) + // owner_token holds the hashed verification token of the calling key. + // Intentionally not declared as a foreign key — the proxy's master key + // has no row in LiteLLM_VerificationToken, so a hard FK would block any + // master-key caller from creating a task. Cleanup of orphaned rows is + // an operator responsibility. owner_token String - owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade) user_id String? team_id String? agent_id String? diff --git a/litellm/proxy/scheduled_tasks/store.py b/litellm/proxy/scheduled_tasks/store.py index 382f469c8fe..f8099380328 100644 --- a/litellm/proxy/scheduled_tasks/store.py +++ b/litellm/proxy/scheduled_tasks/store.py @@ -77,9 +77,7 @@ async def create_task( ) -> Any: return await prisma_client.db.litellm_scheduledtasktable.create( data={ - # Prisma exposes the FK via the relation field (`owner_key`), - # not the scalar column. Use connect to satisfy the input type. - "owner_key": {"connect": {"token": owner_token}}, + "owner_token": owner_token, "user_id": user_id, "team_id": team_id, "agent_id": agent_id, diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index fc6cb6b04d9..24334fc04c1 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -410,7 +410,6 @@ model LiteLLM_VerificationToken { litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id]) object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) jwt_key_mappings LiteLLM_JWTKeyMapping[] - scheduled_tasks LiteLLM_ScheduledTaskTable[] // SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub" // SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2 @@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession { model LiteLLM_ScheduledTaskTable { task_id String @id @default(uuid()) + // owner_token holds the hashed verification token of the calling key. + // Intentionally not declared as a foreign key — the proxy's master key + // has no row in LiteLLM_VerificationToken, so a hard FK would block any + // master-key caller from creating a task. Cleanup of orphaned rows is + // an operator responsibility. owner_token String - owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade) user_id String? team_id String? agent_id String? diff --git a/schema.prisma b/schema.prisma index fc6cb6b04d9..24334fc04c1 100644 --- a/schema.prisma +++ b/schema.prisma @@ -410,7 +410,6 @@ model LiteLLM_VerificationToken { litellm_project_table LiteLLM_ProjectTable? @relation(fields: [project_id], references: [project_id]) object_permission LiteLLM_ObjectPermissionTable? @relation(fields: [object_permission_id], references: [object_permission_id]) jwt_key_mappings LiteLLM_JWTKeyMapping[] - scheduled_tasks LiteLLM_ScheduledTaskTable[] // SELECT COUNT(*) FROM (SELECT "public"."LiteLLM_VerificationToken"."token" FROM "public"."LiteLLM_VerificationToken" WHERE ("public"."LiteLLM_VerificationToken"."user_id" = $1 AND ("public"."LiteLLM_VerificationToken"."team_id" IS NULL OR "public"."LiteLLM_VerificationToken"."team_id" <> $2)) OFFSET $3 ) AS "sub" // SELECT ... FROM "public"."LiteLLM_VerificationToken" WHERE "public"."LiteLLM_VerificationToken"."user_id" = $1 OFFSET $2 @@ -1295,8 +1294,12 @@ model LiteLLM_AdaptiveRouterSession { model LiteLLM_ScheduledTaskTable { task_id String @id @default(uuid()) + // owner_token holds the hashed verification token of the calling key. + // Intentionally not declared as a foreign key — the proxy's master key + // has no row in LiteLLM_VerificationToken, so a hard FK would block any + // master-key caller from creating a task. Cleanup of orphaned rows is + // an operator responsibility. owner_token String - owner_key LiteLLM_VerificationToken @relation(fields: [owner_token], references: [token], onDelete: Cascade) user_id String? team_id String? agent_id String?