Skip to content

fix(proxy): skip prisma-dependent hooks when no database is attached - #36273

Merged
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_dbless_hook_registration
Aug 9, 2026
Merged

fix(proxy): skip prisma-dependent hooks when no database is attached#36273
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_dbless_hook_registration

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Batch create on a DB-less proxy 500s with 'NoneType' object has no attribute 'db'
  • The provider job has already started by then, so it bills
  • The caller never gets the batch id, so no poll or cancel
  • Every retry starts another paid provider job

How it solves it:

  • Hooks that require a database are not registered when none is attached
  • Batch and fine-tuning creates then respond normally on DB-less proxies
  • Uploads asking for target_model_names without a database fail fast with a 400

User Flow

Before: a batch create through a proxy running without a database spends real money and then reports failure

  1. The proxy admin boots the gateway with a Vertex AI model in the config and no DATABASE_URL
  2. A user uploads their batch input with POST http://litellm-domain/v1/files (purpose=batch, custom_llm_provider=vertex_ai) and gets back an id like gs://<bucket>/litellm-vertex-files/publishers/google/models/gemini-3.6-flash/<uuid>
  3. They send POST http://litellm-domain/v1/batches with that id and "custom_llm_provider": "vertex_ai", and get HTTP 500 with {"error":{"message":"'NoneType' object has no attribute 'db'", ...}}
  4. The batch job actually started on Vertex before the 500 and runs to completion billed, but the user never saw its id, so they cannot poll it, cancel it, or fetch its output, and retrying the create starts another paid job

After: the same walk returns the batch object and the batch lifecycle completes without a database

  1. The proxy admin boots the gateway the same way
  2. The user uploads the same input with POST http://litellm-domain/v1/files and gets the same style of id back
  3. The same POST http://litellm-domain/v1/batches now returns 200 with the batch object, a numeric id and "status": "validating"
  4. GET http://litellm-domain/v1/batches/{id}?provider=vertex_ai reaches "status": "completed" and the output sits at the returned output file path

Relevant issues

Resolves #36265

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live e2e proof, no mocks, real Vertex AI spend. Both legs boot a DB-less proxy from its own clean worktree: no DATABASE_URL anywhere (the leg .env carries only GCS settings, grep -c DATABASE_URL .env prints 0, and the boot log prints prisma_client: None), same config (gemini-3.6-flash, project vertex-check-481318, location global), same flow. Only the commit the proxy boots from differs

Before leg, merge base b6e3ff6: 500 after the provider job already started

The upload succeeds:

curl -sS -X POST http://localhost:41783/v1/files -H "Authorization: Bearer sk-1234" \
  -F purpose=batch -F custom_llm_provider=vertex_ai -F file=@batch-input.jsonl

{"id":"gs://litellm_bucket-16/litellm-vertex-files/publishers/google/models/gemini-3.6-flash/c8f46fbd-f175-402f-8df7-ce2b6cfc24e7","bytes":447,...,"purpose":"batch","status":"uploaded",...}

then the batch create 500s and the client never sees the batch id:

curl -sS -i -X POST http://localhost:41783/v1/batches -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{"input_file_id": "gs://litellm_bucket-16/litellm-vertex-files/publishers/google/models/gemini-3.6-flash/c8f46fbd-f175-402f-8df7-ce2b6cfc24e7", "endpoint": "/v1/chat/completions", "completion_window": "24h", "custom_llm_provider": "vertex_ai"}'

HTTP/1.1 500 Internal Server Error
{"error":{"message":"'NoneType' object has no attribute 'db'","type":"internal_server_error","param":"None","code":"500"}}

The proxy log shows the provider call had already succeeded, LiteLLMBatch(id='1667570736853680128', ..., status='validating'), before the hook crashed:

File ".../enterprise/litellm_enterprise/proxy/hooks/managed_files.py", line 231, in store_unified_object_id
    await self.prisma_client.db.litellm_managedobjecttable.upsert(
AttributeError: 'NoneType' object has no attribute 'db'
INFO: 127.0.0.1:59270 - "POST /v1/batches HTTP/1.1" 500 Internal Server Error

and querying Vertex directly confirms the orphaned job exists and bills:

GET https://aiplatform.googleapis.com/v1/projects/vertex-check-481318/locations/global/batchPredictionJobs/1667570736853680128

"state": "JOB_STATE_QUEUED",
"model": "publishers/google/models/gemini-3.6-flash",
"createTime": "2026-08-08T08:50:21.257594Z"

After leg, PR head 855c49d: 200 and pollable to completed

The startup log shows the fix at work, and the pre-fix error string appears zero times in the whole log:

Skipping proxy hook managed_files: it requires a database and no prisma client is configured
Skipping proxy hook managed_vector_stores: it requires a database and no prisma client is configured

Same upload, then the same batch create now returns the batch object:

curl -sS -i -X POST http://localhost:41217/v1/batches -H "Authorization: Bearer sk-1234" -H "Content-Type: application/json" \
  -d '{"input_file_id": "gs://litellm_bucket-16/litellm-vertex-files/publishers/google/models/gemini-3.6-flash/2d937a6b-3daa-4b7e-b460-3d79527495b5", "endpoint": "/v1/chat/completions", "completion_window": "24h", "custom_llm_provider": "vertex_ai"}'

HTTP/1.1 200 OK
{"id":"5186007945736880128","completion_window":"24h","created_at":1786204125,"endpoint":"","input_file_id":"gs://litellm_bucket-16/...","object":"batch","status":"validating",...}

and polling reaches a terminal state in about 3.5 minutes (validating -> in_progress -> completed):

curl -sS "http://localhost:41217/v1/batches/5186007945736880128?provider=vertex_ai" -H "Authorization: Bearer sk-1234"

{"id":"5186007945736880128",...,"status":"completed","output_file_id":"gs://litellm_bucket-16/litellm-vertex-files/publishers/google/models/gemini-3.6-flash/prediction-model-2026-08-08T08:48:45.356899Z/predictions.jsonl",...}

Type

🐛 Bug Fix

Changes

_add_proxy_hooks (litellm/proxy/utils.py) instantiated every registered hook, passing the module-global prisma_client into any constructor that asks for it, and registered the result even when that global was None. Two enterprise hooks have such constructors, managed_files and managed_vector_stores, and the managed-files post-success handler writes every batch and fine-tuning create to the database unconditionally, so on DB-less proxies the write raised AttributeError after the provider had already accepted the job. The fix skips construction and registration of a hook whose constructor requires prisma_client while none is configured, with a debug log naming the skipped hook

Startup ordering makes the skip safe for DB-backed deployments: the lifespan sets the prisma client (proxy_server.py:1002) before hook registration runs (_initialize_startup_logging at proxy_server.py:1068 -> startup_event -> _init_litellm_callbacks, the only path into _add_proxy_hooks), and the function re-imports the global at call time. Every consumer of get_proxy_hook("managed_files") and of get_proxy_hook("managed_vector_stores") already handles None, since that is what non-enterprise installs return today

One consumer needed an explicit decision: POST /v1/files with target_model_names plus a storage backend previously crashed the same way on DB-less proxies after uploading the file. With the hook absent it would have silently returned a file that cannot do target-model routing, so upload_file_to_storage_backend now rejects target_model_names with a 400 naming the database requirement before uploading anything. That also turns the pre-existing silent skip on enterprise-absent deployments into the same explicit 400

Tests: test_lifecycle.py gains regression tests that a prisma_client-requiring hook is skipped (mapping and callbacks) without a database and registered with one, and the skip test fails on the unfixed code. A new test_storage_backend_service.py pins the 400 firing before upload, the no-target_model_names path staying hook-free, and the with-hook path storing the unified id

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR prevents database-dependent enterprise hooks from being registered when no Prisma client is available and rejects storage-backend uploads requiring managed-file routing before uploading.

  • Skips hooks whose constructors require prisma_client on database-less proxy deployments.
  • Adds an explicit 400 response for target_model_names when the managed-files hook is unavailable.
  • Adds regression coverage for hook registration and storage-upload behavior.

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness or security issues identified.

The changed registration logic matches the affected hook signatures, preserves database-backed behavior, and fails target-model storage requests before external upload when required persistence is unavailable.

Important Files Changed

Filename Overview
litellm/proxy/utils.py Adds startup-time filtering for hooks that explicitly require an unavailable Prisma client; the affected production hook signatures are correctly detected.
litellm/proxy/openai_files_endpoints/storage_backend_service.py Adds a pre-upload managed-files capability check for target-model routing and returns a clear client error when unavailable.
tests/test_litellm/proxy/openai_files_endpoint/test_storage_backend_service.py Covers rejection before upload, hook-free uploads without target models, and successful managed-file metadata storage.
tests/test_litellm/proxy/utils/proxy_logging/test_lifecycle.py Adds lifecycle tests proving Prisma-dependent hooks are skipped without a database and registered when a client exists.

Reviews (1): Last reviewed commit: "fix(proxy): skip prisma-dependent hooks ..." | Re-trigger Greptile

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_dbless_hook_registration (855c49d) with litellm_internal_staging (c28cbb8)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (e24a914) during the generation of this report, so c28cbb8 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@mateo-berri
mateo-berri merged commit 6eaeab8 into litellm_internal_staging Aug 9, 2026
83 checks passed
@mateo-berri
mateo-berri deleted the litellm_dbless_hook_registration branch August 9, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: DB-less proxy 500s on batch create after the provider job already started, managed files hook dereferences prisma_client=None

2 participants