Repository navigation
fix(proxy): resolve team callbacks when key logging is empty and report GCS flush failures - #41105
devin-ai-integration[bot] wants to merge 16 commits into
Conversation
…rt GCS flush failures An empty key-level logging list is now treated as unset, so team logging, deprecated team callback_settings and default_team_settings apply. /key/health tests the effective callbacks, flushes the GCS logger explicitly and reports failed uploads. Failed GCS batches stay queued for the next flush instead of being dropped 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>
…key/health Entries that fail AddTeamCallback validation (for example a key entry without callback_vars) are dropped by the request-time resolver. /key/health used to fall through to the next level and call the key healthy with no callbacks. It now returns unhealthy with the offending entry and the validation error Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…stead of falling back to config callbacks Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…itellm_team_callback_resolution
… GCS flush in /key/health Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…flush test passes without a license Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The flush result now carries the ids it uploaded and the ids it failed, so /key/health reports unhealthy only when its own event failed to upload. Another team's broken bucket or a leftover retry in the shared queue no longer marks a working key unhealthy, and the flush walks every pending batch so an event queued behind more than batch_size items is still judged Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ush so an in-flight failure is not missed Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ever retries its own requeued batch Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…s null Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ement endpoints 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>
…alth CodeQL flags the module-level import of ModelResponse from litellm.types.utils as part of a cyclic import; the isinstance check now goes through the already-imported litellm package like the other proxy hooks do. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Real GCS audit at cd322fa: Admin UI Playground request, its object in the real bucket, and the disabled team's zero matches Playground request through the empty logging key Real bucket, one object matches the Playground marker Real bucket, the curl request id chatcmpl-EO81WhenjXChQ56fdwxefqN8REmfi Real bucket, disabled team request, zero matches Team logging settings, key logging settings, Logs detail Recording of the walk-through |
Greptile SummaryThis PR corrects callback inheritance for keys with empty logging metadata and makes GCS delivery failures visible and retryable.
Confidence Score: 5/5The PR appears safe to merge, with no outstanding findings in the current review state. No new changes exist since the previous review SHA, both previous threads are resolved, and the current diff contains no confirmed rule violation or actionable regression.
|
| Filename | Overview |
|---|---|
| litellm/integrations/gcs_bucket/gcs_bucket.py | Adds locked flush reporting and retains failed GCS events for later retry while preserving bounded-queue behavior. |
| litellm/proxy/litellm_pre_call_utils.py | Treats empty key logging metadata as unset while retaining team-level empty logging as an explicit disable state. |
| litellm/proxy/management_endpoints/key_management_endpoints.py | Resolves effective request callbacks during key health checks and reports callback validation or GCS upload failures. |
| litellm/types/integrations/gcs_bucket.py | Introduces an immutable result type containing sent and failed GCS event identifiers. |
| tests/test_litellm/integrations/gcs_bucket/test_gcs_bucket.py | Covers failed-upload retention, retry behavior, multi-batch flushing, individual uploads, and queue overflow. |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | Covers effective callback selection, invalid metadata, GCS health attribution, and in-flight flush synchronization. |
| tests/test_litellm/proxy/test_litellm_pre_call_utils.py | Verifies key/team callback precedence and explicit team logging disable behavior. |
| tests/test_litellm/integrations/otel/test_otel_v2_destinations.py | Updates OTel destination expectations to match empty-key callback inheritance. |
| litellm/proxy/_types.py | Generalizes health callback names to a read-only sequence. |
| ui/litellm-dashboard/src/lib/http/schema.d.ts | Synchronizes generated key-health API documentation with the expanded callback checks. |
Reviews (2): Last reviewed commit: "refactor(proxy): read ModelResponse off ..." | Re-trigger Greptile
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cd322fa. Configure here.
There was a problem hiding this comment.
Devin Review found 2 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
TLDR
Problem this solves:
logginglist silences its team's and the config's callbacks/key/healthonly looked at key metadata, so it said healthy while nothing reached GCSHow it solves it:
logginglist now means unset: the team's callbacks apply, then the config'slogginglist still means the team turned logging off/key/healthsay unhealthy/key/healthtests the callbacks a real request would usegcs_bucketit flushes the queue now and reports whether its own test event reached the bucket. Another key's queued failure does not make this key unhealthy, the flush drains everything queued rather than one batch, and it waits for any periodic flush already in flight so a failure that flush hits is not missedBehavior change, no flag:
"metadata": {"logging": []}used to log nowhere. After this PR it logs through its team's callbacks, then the config's. The Admin UI writes that empty list on every key created without a logging integration, so it never expressed intent to disable, andlitellm_disabled_callbacksremains the way to turn callbacks off on a key. A team'slogging: [](what/team/{id}/disable_loggingwrites) still disablesReview notes:
User Flow
Before: a team admin sets GCS logging for the team in the proxy config, then a member creates a key from the Admin UI and none of that key's requests ever land in the bucket
default_team_settingswithsuccess_callback: ["gcs_bucket"]forteam-gcsin the config and restarts the proxyteam-gcsat https://litellm-domain/ui/?page=api-keys without picking a logging integration, so the key is stored with"metadata": {"logging": []}chatcmpl-...response{"key":"healthy","logging_callbacks":{"callbacks":[],"status":"healthy",...}}, which says nothing is wrongloggingmetadata at all does log to GCS, but when the bucket rejects one upload those events are gone: the next flush is silent and/key/healthstill says healthyAfter: the same key logs to the team's bucket, and
/key/healthtells the truth about GCS"metadata": {"logging": []}YYYY-MM-DD/batch-....ndjsonobject containing thatchatcmpl-...id{"key":"healthy","logging_callbacks":{"callbacks":["gcs_bucket"],"status":"healthy",...}}/key/healthreturns{"key":"unhealthy",...,"details":"... GCS upload failed for the /key/health event and 0 other event(s), 0 uploaded ..."}Relevant issues
Linear ticket
Resolves LIT-7072
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito 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
This is the live audit run against a real Google Cloud Storage bucket, replacing the earlier fake-gcs-server run. Every request below is a real provider call. Two proxies ran side by side from the same config and the same key set: the Before leg from the merge base with main on
:20173, the After leg from this PR's tip on:20172, each with its own Postgres database, 2 workers,LITELLM_DISABLE_NO_REDIS_WARNING=true,--use_v2_migration_resolver, no--detailed_debug,GCS_FLUSH_INTERVAL=5. The destination is a real GCS bucket (name redacted as<BUCKET>) written with a real service account through the proxy's own GCS logger; objects land underlitaudit-7072gcs/<leg>/. Traffic tostorage.googleapis.com:443goes through a local TCP forwarder (hosts override) so the chaos cases can cut or slow the destination; the forwarder only copies bytes and TLS still terminates at Google. No mocks anywhere in the product path: the onlymock_responseis the onePOST /key/healthitself sends by designConfig:
Keys, created the way the Admin UI does it (
"metadata":{"logging":[]}) unless stated otherwise.$K_YAML_EMPTYis onteam-gcs(callbacks from the YAML above);$K_YAML_NULLis the same team with"logging": null;$K_YAML_MISSINGis the same team with nologgingkey;$K_TEAM_EMPTYis onteam-meta, whosemetadata.loggingis[{"callback_name":"gcs_bucket","callback_type":"success_and_failure","callback_vars":{}}];$K_TEAM_SUCCESSand$K_TEAM_FAILUREare on teams whosecallback_typeissuccessandfailure;$K_KEY_ONLYcarries its ownsuccess_and_failureentry on a team with no settings;$K_KEY_OVER_TEAMcarries asuccessentry onteam-meta;$K_DEPRECATEDis on a team using the deprecatedmetadata.callback_settingsshape;$K_TEAM_DISABLEDis on a team whosemetadata.loggingis[];$K_NO_TEAMhas no team. Every "landed" claim was checked by listing the real bucket through the storage JSON API and matching the response id inside the.ndjsonpayloads (streaming/v1/responsespayloads carry an internal id, so those cells match on a unique marker embedded in the prompt instead; the base leg has the same id shape). Team and key metadata sit in a 60 s per-worker in-memory cache (no Redis), so settings changes are asserted after a 65 s wait. The full matrix (76 executed rows, exact commands, ids, object names) is in the audit report shared with the requesterBefore (d4a72e7)
Empty key logging on a team with YAML callbacks
curl -s -X POST localhost:20173/v1/chat/completions -H "Authorization: Bearer $K_YAML_EMPTY" -H 'Content-Type: application/json' -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"Reply with the single word ok. marker litaudit-base-B1-1789419396"}]}'-> 200,"id":"chatcmpl-EO8161wxcOHYOJDFA5VloXOXrWuf7"litaudit-7072gcs/base/after the flush interval: no object contains that idEmpty key logging on a team with metadata callbacks
:20173with$K_TEAM_EMPTY-> 200,"id":"chatcmpl-EO8175NyqLOmgmxxoGGU87bGoKXHf"Control key with no logging entry
:20173with$K_YAML_MISSING-> 200,"id":"chatcmpl-EO819rFy5osHtEViX1LrcDm9uOVLD"2026-09-14/batch-1789419402442-66e6cf9f.ndjsoncontains that id, so the team's callbacks work when the key says nothing at all/key/health for empty and null key logging
curl -s -X POST localhost:20173/key/health -H "Authorization: Bearer $K_YAML_EMPTY"-> 200{"key":"healthy","logging_callbacks":{"callbacks":[],"status":"healthy","details":"No logger exceptions triggered, system is healthy. Manually check if logs were sent to [] "}}curl -s -X POST localhost:20173/key/health -H "Authorization: Bearer $K_YAML_NULL"-> 500{"error":{"message":"Key health check failed: 'NoneType' object is not iterable","type":"internal_server_error","param":"None","code":"500"}}/key/health while the destination is unreachable
down(every connection tostorage.googleapis.com:443refused), 6 s waitcurl -s -X POST localhost:20173/key/health -H "Authorization: Bearer $K_YAML_MISSING"-> 200{"key":"healthy","logging_callbacks":null}, nothing names the failed uploadokEndpoints, clients, modes, sad paths, edge cases, chaos and Admin UI
After (cd322fa)
Empty key logging on a team with YAML callbacks
curl -s -X POST localhost:20172/v1/chat/completions -H "Authorization: Bearer $K_YAML_EMPTY" -H 'Content-Type: application/json' -d '{"model":"gpt-5.6","messages":[{"role":"user","content":"Reply with the single word ok. marker litaudit-head-H1-1789419421"}]}'-> 200,"id":"chatcmpl-EO81WhenjXChQ56fdwxefqN8REmfi"litaudit-7072gcs/head/2026-09-14/batch-1789419426166-7aac9fe9.ndjsoncontains that id,status: success, exactly one object"stream": true(-N, stream read to the end) ->chatcmpl-EO81WASVgvHw3tDxugwRqIMeohvld, one objectEmpty key logging on a team with metadata callbacks
openai.OpenAI(base_url="http://localhost:20172", api_key=$K_TEAM_EMPTY).chat.completions.create(model="gpt-5.6", ...)->chatcmpl-EO81Y2urEkytVdqS1mpcs6zI8QHG1, one object (batch-1789419426166-7aac9fe9.ndjson)stream=True->chatcmpl-EO81ZXLe4fP9JyBqO15Ia9f7qXvmg, one object (batch-1789419427053-65c762c6.ndjson)Control key with no logging entry
:20172with$K_YAML_MISSING-> 200,chatcmpl-EO81t2D2flmQ8dCPEbsfI4HkVTUNM, one object (batch-1789419446817-4227c0f8.ndjson), unchanged from Before/key/health for empty and null key logging
curl -s -X POST localhost:20172/key/health -H "Authorization: Bearer $K_YAML_EMPTY"-> 200{"key":"healthy","logging_callbacks":{"callbacks":["gcs_bucket"],"status":"healthy","details":"No logger exceptions triggered, system is healthy. Manually check if logs were sent to gcs_bucket"}}$K_YAML_NULL-> 200,callbacks: ["gcs_bucket"],healthy(was a 500 Before)$K_YAML_MISSING,$K_TEAM_EMPTY,$K_KEY_ONLYand$K_DEPRECATED-> 200,callbacks: ["gcs_bucket"],healthy$K_TEAM_DISABLED,$K_NO_TEAMand a key on a team with no settings -> 200{"key":"healthy","logging_callbacks":null}, so an explicit team disable stays disabled/key/health while the destination is unreachable
down, then a 36 request burst (below) andcurl -s -X POST localhost:20172/key/health -H "Authorization: Bearer $K_YAML_EMPTY"-> 200{"key":"unhealthy","logging_callbacks":{"callbacks":["gcs_bucket"],"status":"unhealthy","details":"Logger exceptions triggered, system is unhealthy: GCS upload failed for the /key/health event and 18 other event(s), 0 uploaded GCS Bucket error logging batch payload to GCS bucket: Cannot connect to host storage.googleapis.com:443 ..."}}ok, one flush interval later the same call -> 200healthy, and the health event itself is in the bucket:20174withGCS_BUCKET_NAMEset to a bucket that does not exist. A chat completion with$K_YAML_EMPTY-> 200chatcmpl-EO8EIeMEhyrsNQ0mj3Z2InaBbyuYH(caller unaffected), then/key/health-> 200{"key":"unhealthy",...,"details":"... GCS upload failed for the /key/health event and 1 other event(s), 0 uploaded GCS Bucket error logging batch payload to GCS bucket: Client error '404 Not Found' for url 'https://storage.googleapis.com/upload/storage/v1/b/<missing bucket>/o...'"}:20175pointed at a public bucket this service account cannot write. Chat completion -> 200chatcmpl-EO8F2um3q2B86S8zS7HRVZZmM7c62,/key/health-> 200unhealthywith the403 Forbiddentext from GCS indetailsEndpoints, clients, modes, sad paths, edge cases, chaos and Admin UI
/v1/chat/completions$K_YAML_EMPTYchatcmpl-EO81WhenjXChQ56fdwxefqN8REmfi/v1/chat/completions$K_YAML_EMPTYchatcmpl-EO81WASVgvHw3tDxugwRqIMeohvld/v1/chat/completions$K_TEAM_EMPTYchatcmpl-EO81Y2urEkytVdqS1mpcs6zI8QHG1/v1/chat/completions$K_TEAM_EMPTYchatcmpl-EO81ZXLe4fP9JyBqO15Ia9f7qXvmg/v1/chat/completions$K_KEY_ONLYchatcmpl-EO81as7hNsb9kP6UBhnkMLFj9qY3f/v1/chat/completions$K_KEY_ONLYchatcmpl-EO81boiPbtxkJs9b75v0G45mX1nsP/v1/messages$K_YAML_EMPTYmsg_011Cf3zzh8rXJp7CddWd72WA/v1/messages$K_TEAM_EMPTYmsg_011Cf3zznFRf2yQQtNMseR4q/v1/messages$K_YAML_NULLmsg_011Cf3zzr19uYKER6YH6dKRk/v1/messages$K_TEAM_MISSINGmsg_011Cf3zzvaV5MKssWS8yS2nr/v1/messages$K_KEY_ONLYmsg_011Cf4111b6khdsMCFRP2WQz/v1/messages$K_YAML_MISSINGmsg_011Cf4115z26ssZ17BuuSs3n/v1/responses$K_YAML_EMPTYresp_2cLWn-ROqOw6wY7wG4lIvDGaC0W30yco4bI6fY3AKfKiiX8Y.../v1/responses$K_TEAM_EMPTYlitaudit-head-H14-.../v1/responses$K_TEAM_SUCCESSresp_DfM298wOeouAIw4XsLeVqfIZ3LStiibxI3IuA_ZhwVzJfPY8.../v1/responses$K_KEY_OVER_TEAMlitaudit-head-H16-.../v1/responses$K_DEPRECATEDresp_KvL10PY6AKUI5gBBj3n6djUdZ8zXw9tCSLZCxca0MLHYbSO_.../v1/responses$K_YAML_NULLlitaudit-head-H18-.../v1/chat/completions$K_DEPRECATEDchatcmpl-EO81s151pPyHvk7Bgw02nofEhMCPpbroken-model:successteam uploads the 200 (chatcmpl-EO81uHQceKdrj1OzTnveBZOxqp20z) and skips the 401;failureteam skips the 200 (chatcmpl-EO81yCLAVjcF9ESo5gVXe4wj5rZcV) and uploads the error (one failure object,batch-1789419452009-eab89d1b.ndjson);success_and_failurevia YAML uploads both;$K_KEY_OVER_TEAM(keysuccesson asuccess_and_failureteam) uploads no failure object, so a key that says something still wins. After the first 401 the router cools the deployment down, so later cells saw its 429No deployments availableinstead of the 401; both reached the caller in the response body$K_TEAM_DISABLEDchat (chatcmpl-EO81yqnoJ7kYn0ex8hvseWwapPaQ4) and/v1/messages(msg_011Cf412RChmTXBxEp3Hf1a5) -> 200, zero objects.$K_NO_TEAMand a key on a team with no settings -> 200, zero objectsInvalid model name passed in model=no-such-model-litaudit, no failure object (the router rejects it before any provider call, same as Before);max_tokens: 99999999-> 400max_tokens is too large: 99999999. This model supports at most 128000, one failure object with the marker; a key logging entry withoutcallback_vars-> request 200 and/key/health200{"key":"unhealthy",...,"details":"Invalid callback metadata, requests ignore these entries: not_a_callback: callback_vars Field required"}; a well formed entry naming a callback no logger implements -> request 200, zero objects,/key/healthsayshealthyfor["not_a_callback"], unchanged from Before and tracked as LIT-7693; a keycallback_vars.gcs_bucket_namepointing at a missing bucket -> the upload still goes to the process bucket because that var is request-blocked, unchanged from Before and tracked as LIT-7692; an entry with nocallback_name-> request 200,/key/health500Key health check failed: callback_name is required in key_logging, same handler as Before (every health exception becomes a 500), listed under caveats$K_YAML_EMPTY-> five distinct ids, one object each.POST /team/team-plain/callbackaddinggcs_bucketwhile a request fires every 8 s: every request from 0.9 s after the update landed once.POST /team/team-plain/disable_loggingwhile traffic flows: one request 18.6 s after the disable still landed (the other worker's 60 s team cache, same window as Before), nothing after the TTL.POST /key/updateswitching$K_TEAM_EMPTY's own logging tofailureand back to[]with a 65 s wait after each update: success objects 1 / 0 / 1/key/healthduring the cut reportedunhealthy(step 1 of the previous case), afterokevery one of the 36 ids landed exactly once, zero duplicates, zero missing. Slow destination (3 s added to every chunk sent to GCS): 18/18 landed exactly once and three back to back/key/healthcalls racing the periodic flush returnedhealthyin 10.1 s, 5.9 s and 5.0 s, no deadlock.SIGKILLof one of the two head workers mid burst: the other worker kept serving, the supervisor spawned a replacement, 16/24 callers got 200, 15 of those landed once, the one event still sitting in the killed worker's in-memory queue was lost. Proxy restart mid burst: 24/24 callers got 200 before the stop, the 24 events still queued in memory did not survive the process, no duplicates, proxy back in 24.4 s and serving 200http://localhost:20172/ui(admin plus master key).?page=teams, openteam-meta, Logging Settings tab:gcs_bucket, Success & Failure.?page=api-keys, openK_YAML_EMPTY, Settings, Logging Settings: no integrations (the dashboard renders the stored"logging": []as an empty state; the API transcript above shows the literal metadata).?page=logs, click the row forchatcmpl-EO81WhenjXChQ56fdwxefqN8REmfi: Success,gpt-5.6, cost and token counts, teamteam-gcs. Playground, virtual keyK_YAML_EMPTY,/v1/chat/completions,gpt-5.6, promptReply with exactly: confirmed litaudit-ui-20260914-225430-> replyconfirmed litaudit-ui-20260914-225430, response idchatcmpl-EO8lVw2TZYJjLcVKHsXtDMCxUgj1a, and that marker is in exactly one object in the real bucket (litaudit-7072gcs/head/2026-09-14/batch-1789422277664-a1c91631.ndjson, 82 objects listed, 1 match). The same viewer for the$K_TEAM_DISABLEDrequest shows 82 objects and 0 matches. Google Cloud console needs an interactive Google login this session does not have, so the bucket pages are a local read-only page that lists the real bucket through the storage JSON API with the same service account. The dashboard has no button that callsPOST /key/health, so that endpoint is proven with the curl steps above. Screenshots and the recording are in the PR comment linked below, kept out of the description on purposeNot run: the OTel v2 destination fallback (no OTel collector in this rig, unit tested only)
Type
🐛 Bug Fix
Caveats (if any)
Medium
/key/healthnow runs the 2 second test log for keys whose callbacks come from the team or config, not only from key metadata. It used to return instantly for those keysLow
detailsstring still carries the captured logger traceback, same as before. The GCS verdict for the health check's own event is prepended to itGCSFlushResultnow carriessent_ids/failed_ids;sentandfailedcounts remain as properties, andflush_queue()still returnsNoneflush_lock, so/key/healthwaits for a periodic flush that is mid-upload instead of racing it.flush_queue_and_report()takes every event off the queue before uploading, sends them inbatch_sizegroups, and leaves any failed group on the queue for the next flush instead of retrying it in the same call. The periodic flush still sends one batch per intervallitellm_disabled_callbacks; an empty keylogginglist no longer does that. The OTel v2 destination resolver follows the same rule/team/{id}/disable_logging, a proxy worker keeps serving the team's old callbacks until its 60 second team cache expires, same as before this PR (seen once at 18.6 s in the audit)callback_namemakes/key/healthreturn 500 with a clear message instead of 400, same handler as before this PRhealthy(LIT-7693), and a per keycallback_vars.gcs_bucket_namestill cannot redirect the upload (LIT-7692). Both unchanged from the base and out of scope heretests/test_litellm/integrations/gcs_bucket/test_gcs_bucket.pyis new becausegcs_bucket.pyhad no mapped test file (test_gcs_bucket_base.pycovers the base class)Review and CI status (at cd322fa)
litellm_internal_staging, then moved tomainon request, where the requiredGuard main branchcheck rejects any head that is notlitellm_internal_stagingorlitellm_hotfix_*, so the branch was pushed again aslitellm_hotfix_7072_team_callbacks. Review threads and the Greptile and Bugbot history also live on fix(proxy): resolve team callbacks when key logging is empty and report GCS flush failures #40922; the visual evidence for the real GCS run is a comment on this PRtest_key_management_endpoints.py, so 8bc772d merges main into the branch and keeps both imports (asynciofrom this PR,SimpleNamespacefrom main). No product code changed in that merge; the audit and review evidence below are for cd322fa, the last commit that touched product code. CI at 8bc772d: 89 checks pass, 1 skipped (stage-mirror e2e), 0 fail; CodeQL passes there. main then added aCustomKeyPolicyRequestimport and new bulk-update tests at the end of the same test file, so acba499 merges main again and keeps both sides (the import next to this PR'sProxyConfigimport, main's tests after this PR's health tests). Again no product code changed; the focused key-management tests pass at that tip (49 passed) and CI at acba499 is 89 pass, 1 skipped, 0 fail. The schema.d.ts sync check that failed against staging (two stalesoft_budgetdoc lines that main had already regenerated, chore(ui): regenerate schema.d.ts after soft_budget left the user endpoint docstrings #40987, now closed) does not apply to mainloggingbehavior change be flagged, now stated in the TLDR above. The other says concurrent health checks share the root logger; the base branch attaches the same handler to the root logger for every health check, so this PR does not change that. Both stood down in-thread, Greptile withdrew both on re-trigger, and its second summary at cd322fa is 5/5 with no outstanding findings. On fix(proxy): resolve team callbacks when key logging is empty and report GCS flush failures #40922 the same tip also scored 5/5 with no open threadspy/cyclic-importandpy/unsafe-cyclic-importqueries, and all inkey_management_endpoints.pyor at the other end of a cycle it already sits in (proxy_server.py:526-531,internal_user_endpoints.py:54-56). None is a runtime problem. TheCustomLoggeralert (line 165) points at an import underTYPE_CHECKING, which never executes. Theencrypt_callback_varsalert (line 74) is the same module-level import main has at the same line, minus names this PR stopped using. The four "begins an import cycle" notes are function-level imports ofproxy_server,litellm_pre_call_utils,litellm_loggingandgcs_bucket; the file already importsproxy_serverat 19 function-level sites on main for the same reason (proxy_serverimports this module), andimport litellmloadslitellm_loggingandgcs_bucketbefore any proxy module. The threeinternal_user_endpointsand threeproxy_serveralerts describe that pre-existing cycle. CodeQL also fails on another open main PR at the time of writing (feat(router): cool deployment down until quota-reset time stated in error body #41090)litellm_internal_staging, Veria reviewed one (fix(mcp): require admission for delegated OAuth #40923, an MCP auth change), so a missing Veria verdict on this PR is treated as not applicable, not as pending_log_json_data_on_gcsreturns the body without raising; not reachable,AsyncHTTPHandler.postcallsraise_for_status()first, and a MockTransport returning 403 makes_send_grouped_batchreturn(0, 1)and_send_single_log_itemreturnFalse. The other repeats the fix(proxy): resolve team callbacks when key logging is empty and report GCS flush failures #40922 thread that a failure-only callback goes untested because the health check's mock completion always succeeds. That is how the base branch behaves too, this PR only changes which callbacks the health check selects and how GCS results are read back, and treating an absent event id as a failure would misreport events a periodic flush already uploadedFinal Attestation
ran /live-pr-risk and found no regressions/backward incompatible risks
isinstancetarget is imported fromLink to Devin session: https://app.devin.ai/sessions/42a8e9b831c141108fd010826df31fe7
Open in Devin Desktop: https://app.devin.ai/desktop/session/42a8e9b831c141108fd010826df31fe7?variant=devin
Requested by: @yucheng-berri