fix(proxy): stop shipping the literal string "None" as error type and param - #39536
mateo-berri merged 7 commits into
Conversation
… param The proxy's exception tails defaulted `type` and `param` to the four-character string "None", which is neither a known OpenAI error type nor the JSON null the nullable `param` field is typed as, so a client's error handler matched nothing and fell into its generic branch. Lifts the helpers PR #39521 added for the unified LLM endpoints into litellm/proxy/common_utils/openai_error_payload.py and calls them from the file, rerank, image, realtime, anthropic, and pass-through route families, plus the shared handle_exception_on_proxy handler that the management, batches, fine-tuning, credential, SCIM, guardrail, and customer routes funnel through. The remaining families (proxy_server, auth, health, spend tracking, and management endpoints) follow in separate PRs so each slice stays QA'able on a live proxy.
Greptile SummaryThis PR centralizes OpenAI-compatible proxy error shaping and applies it across files, rerank, images, realtime, Anthropic, pass-through, streaming, and shared exception handlers.
Confidence Score: 5/5The PR appears safe to merge; no actionable new issue or outstanding previous finding remains. All previous threads were resolved or correctly withdrawn, the current head is unchanged since the previous review, and the full diff introduces no confirmed rule violation or merge-blocking behavior.
|
| Filename | Overview |
|---|---|
| litellm/proxy/common_utils/openai_error_payload.py | Introduces shared helpers for preserving exception status codes and producing OpenAI-compatible error type and parameter fields. |
| litellm/proxy/common_request_processing.py | Reuses the shared error helpers in HTTP exception conversion, SSE errors, and request-processing failure paths. |
| litellm/proxy/openai_files_endpoints/files_endpoints.py | Normalizes error payloads and preserves carried statuses across all files endpoint exception tails. |
| litellm/proxy/realtime_endpoints/endpoints.py | Applies consistent error typing and nullable parameters to realtime client-secret, WebRTC, and transcription failures. |
| litellm/proxy/pass_through_endpoints/pass_through_endpoints.py | Normalizes pass-through error payloads while retaining exception status codes and custom headers. |
| tests/test_litellm/proxy/common_utils/test_openai_error_payload.py | Covers status-to-type mapping, nullable parameters, carried fields, and stringified ProxyException status codes. |
| tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py | Adds route-level regressions for validation failures, missing managed files, and in-route status preservation. |
Reviews (7): Last reviewed commit: "test(proxy): type the realtime WebRTC fi..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…itellm_openai_error_payload_non_llm_routes # Conflicts: # litellm/proxy/anthropic_endpoints/endpoints.py # litellm/proxy/image_endpoints/endpoints.py
…AI error payload
error_status_code only read status_code, so a ProxyException raised
before routing (which stores its status as the string code) answered
500 with its 4xx type through the rerank, images, realtime, files, and
pass-through tails. It now falls back to a decimal code. A 408 maps to
timeout_error instead of invalid_request_error.
Tail regressions for rerank, images, realtime calls, and the chat
pass-through fail at the merge base with ('None', 'None'); the new
files-test helpers are fully typed.
|
bugbot run |
…ute status on the files and realtime tails
ParBproject
left a comment
There was a problem hiding this comment.
There’s one edge case in the shared helper that seems to leave the original bug reachable: openai_error_type() accepts any str as authoritative, including the literal string "None". That means any existing ProxyException/provider exception that already carries type="None" will still serialize "type": "None", even though this module’s stated contract is to eliminate that value. The new tests only cover an exception with no type attribute / None (non-string), so they won’t catch a legacy string sentinel. I’d suggest treating at least "None" (and probably empty strings) as missing and falling back to the status mapping, with a regression test such as a carrier whose type = "None". The same consideration may apply to openai_error_param() if legacy exceptions carry param="None".
… of a bare Callable
|
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 b3bcd71. Configure here.
2b9a69d
into
litellm_internal_staging
TLDR
Problem this solves:
/v1/filesroutes answer"type": "None"and"param": "None"paramis typed nullable, so"None"is plain wrong thereHow it solves it:
typefalls back to what the HTTP status stands forparamserializes as JSONnull/v1/filesroute, failing without the fixUser Flow
Before: a developer whose app classifies gateway errors by
error.typegets the stringNoneon every/v1/filesfailure, so each one lands in their catch-all branchPOST https://litellm-domain/v1/files(purpose=batch,target_model_names=gpt-5-mini) and get HTTP 200 with a long gateway file idDELETE https://litellm-domain/v1/files/{file_id}and get HTTP 200 with the file objectGET https://litellm-domain/v1/files/{file_id}(or a retried delete, orGET .../content) answers HTTP 404{"error":{"message":"File not found: <file_id>","type":"None","param":"None","code":"404"}}error.type, sees the stringNone, matches no OpenAI type, and reports "unknown error" instead of "that file is gone"error.paramto name the offending field and renders "problem with field None", because a string arrived where JSONnullwas expectedGET https://litellm-domain/v1/files?target_model_names=gpt-5-mini,gpt-5-nanoand an upload with onlyexpires_after[anchor]answer HTTP 400 with the same two stringsPOST https://litellm-domain/v1/rerank,POST https://litellm-domain/v1/images/generations,POST https://litellm-domain/v1/realtime/client_secrets, and a configured pass-through route carries the same two stringsPOST https://litellm-domain/v1/images/generationsthat times out upstream answers HTTP 408 with"type": null, while the same timeout onPOST https://litellm-domain/v1/chat/completionsanswersinvalid_request_error, so the same failure classifies differently per routePOST https://litellm-domain/v1/realtime/transcription_sessionsfor a model the gateway does not serve, a WebRTC offer toPOST https://litellm-domain/v1/realtime/callsthat the provider rejects, and a configured adapter route all answer the same two stringsAfter: the same failures carry a real OpenAI error type and a JSON
nullparam, so the handler they already wrote classifies themPOST https://litellm-domain/v1/files(purpose=batch,target_model_names=gpt-5-mini) and get HTTP 200 with a long gateway file idDELETE https://litellm-domain/v1/files/{file_id}and get HTTP 200 with the file objectGET https://litellm-domain/v1/files/{file_id}(or a retried delete, orGET .../content) answers HTTP 404{"error":{"message":"File not found: <file_id>","type":"invalid_request_error","param":null,"code":"404"}}invalid_request_errorand tells the caller the file is goneerror.param, getsnull, and correctly reports that no single field was namedGET https://litellm-domain/v1/files?target_model_names=gpt-5-mini,gpt-5-nanoand an upload with onlyexpires_after[anchor]answer HTTP 400 withinvalid_request_errorandnullPOST https://litellm-domain/v1/rerank,POST https://litellm-domain/v1/images/generations, andPOST https://litellm-domain/v1/realtime/client_secretscarryinvalid_request_erroron a 400, the pass-through route carriesinternal_server_erroron its 500, all withparamasnull"type": "invalid_request_error"and"param": null, the same shape on both routesinvalid_request_errorwithparamasnullRelevant issues
Fixes #40135
Linear ticket
Resolves LIT-7129
Part of LIT-6829
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
Both legs run the same config against the same Postgres, each as one proxy with 2 uvicorn workers on its own random port, and differ only in the commit the proxy was booted from: Before is the merge base with
litellm_internal_staging(82e6b84f5a), After is this PR's tip (b3bcd715e0). The file uploads and deletes hit the real OpenAI files API, and the live-traffic case is a real Anthropic completion, so both proxies were serving real provider traffic, not just error pathsThe
/v1/messagescase answers the Anthropic envelope on both sides (staging now shapes it from the status code), so it is a regression check rather than a fix case. Same for the credentials case: onlyparamchanges there, the shared handler'stypeis #39555's jobShared config (
lit7129_qa_config_r3.yaml), batch payload (batch.jsonl), and boot command:{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-5-mini", "messages": [{"role": "user", "content": "say hi"}]}}Before (82e6b84)
live traffic (claude-sonnet-5)
curl -sS -X POST http://127.0.0.1:$PORT/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"Say ok"}]}' | jq "{model, content: .choices[0].message.content, usage}"{ "model": "claude-sonnet-5", "content": "Ok", "usage": { "completion_tokens": 26, "prompt_tokens": 10, "total_tokens": 36, "completion_tokens_details": { "reasoning_tokens": 20, "text_tokens": 6 }, "prompt_tokens_details": { "cached_tokens": 0, "text_tokens": 10, "cache_write_tokens": 0, "cache_creation_tokens": 0, "cache_creation_token_details": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 } }, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "inference_geo": "global", "service_tier": "standard" } }files: list and upload validation
curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:$PORT/v1/files?target_model_names=gpt-5-mini,gpt-5-nano" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:$PORT/v1/files -H "Authorization: Bearer $LITELLM_MASTER_KEY" -F purpose=batch -F target_model_names=gpt-5-mini -F "expires_after[anchor]=created_at" -F file=@batch.jsonlfiles: deleted file (get, delete, content)
FILE_ID=$(curl -sS http://127.0.0.1:$PORT/v1/files -H "Authorization: Bearer $LITELLM_MASTER_KEY" -F purpose=batch -F target_model_names=gpt-5-mini -F file=@batch.jsonl | jq -r .id); echo "$FILE_ID"curl -sS -w "\nHTTP %{http_code}\n" -X DELETE "http://127.0.0.1:$PORT/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:$PORT/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" -X DELETE "http://127.0.0.1:$PORT/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:$PORT/v1/files/$FILE_ID/content" -H "Authorization: Bearer $LITELLM_MASTER_KEY"files: unknown provider id
curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:$PORT/v1/files/file-doesnotexist -H "Authorization: Bearer $LITELLM_MASTER_KEY"rerank
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/rerank -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-rerank-model","query":"hi","documents":["a","b"]}'images
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-image-model","prompt":"a cat"}'realtime
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/realtime/client_secrets -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"session":{"type":"realtime","model":"no-such-realtime"}}'anthropic /v1/messages
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","max_tokens":16,"messages":"not-a-list"}'pass-through
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/unreachable-upstream -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"hi":"there"}'shared handler (credentials)
curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:$PORT/credentials/by_name/nope -H "Authorization: Bearer $LITELLM_MASTER_KEY"After (b3bcd71)
live traffic (claude-sonnet-5)
curl -sS -X POST http://127.0.0.1:50132/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"Say ok"}]}' | jq "{model, content: .choices[0].message.content, usage}"{ "model": "claude-sonnet-5", "content": "Ok", "usage": { "completion_tokens": 24, "prompt_tokens": 10, "total_tokens": 34, "completion_tokens_details": { "reasoning_tokens": 18, "text_tokens": 6 }, "prompt_tokens_details": { "cached_tokens": 0, "text_tokens": 10, "cache_write_tokens": 0, "cache_creation_tokens": 0, "cache_creation_token_details": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 } }, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "inference_geo": "global", "service_tier": "standard" } }files: list and upload validation
curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:50132/v1/files?target_model_names=gpt-5-mini,gpt-5-nano" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:50132/v1/files -H "Authorization: Bearer $LITELLM_MASTER_KEY" -F purpose=batch -F target_model_names=gpt-5-mini -F "expires_after[anchor]=created_at" -F file=@batch.jsonlfiles: deleted file (get, delete, content)
FILE_ID=$(curl -sS http://127.0.0.1:50132/v1/files -H "Authorization: Bearer $LITELLM_MASTER_KEY" -F purpose=batch -F target_model_names=gpt-5-mini -F file=@batch.jsonl | jq -r .id); echo "$FILE_ID"curl -sS -w "\nHTTP %{http_code}\n" -X DELETE "http://127.0.0.1:50132/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:50132/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" -X DELETE "http://127.0.0.1:50132/v1/files/$FILE_ID" -H "Authorization: Bearer $LITELLM_MASTER_KEY"curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:50132/v1/files/$FILE_ID/content" -H "Authorization: Bearer $LITELLM_MASTER_KEY"files: unknown provider id
curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:50132/v1/files/file-doesnotexist -H "Authorization: Bearer $LITELLM_MASTER_KEY"rerank
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/rerank -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-rerank-model","query":"hi","documents":["a","b"]}'images
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-image-model","prompt":"a cat"}'realtime
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/realtime/client_secrets -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"session":{"type":"realtime","model":"no-such-realtime"}}'anthropic /v1/messages
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","max_tokens":16,"messages":"not-a-list"}'pass-through
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/unreachable-upstream -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"hi":"there"}'shared handler (credentials)
curl -sS -w "\nHTTP %{http_code}\n" http://127.0.0.1:50132/credentials/by_name/nope -H "Authorization: Bearer $LITELLM_MASTER_KEY"The second pass covers the behaviors the first review round changed. The
aftercursor case is a regression check:list_filesre-raises aProxyExceptionas is, so it answers the same 400 on both legs. The timeout cases show a 408 keepinginvalid_request_erroron both routes, where the Before leg's images route answerednull. The other in-route rejection fix (aProxyExceptionraised inside a guard-less tail keeping its 4xx status instead of answering 500) has one trigger on these routes,get_file_contentreading a file back from a configured storage backend, which needs a managed file row withstorage_backendandstorage_url; it is pinned bytest_get_file_content_keeps_the_status_of_a_rejection_raised_inside_the_routeand the rerank route testtest_a_rejection_raised_before_routing_keeps_its_own_status, both of which answer 500 without the fixBefore (82e6b84)
rejection raised inside the route (files list, unknown after cursor)
curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:$PORT/v1/files?after=file-doesnotexist" -H "Authorization: Bearer $LITELLM_MASTER_KEY"timeout (images, request timeout 0.001)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","prompt":"a cat","timeout":0.001}'timeout (chat completions, request timeout 0.001)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","messages":[{"role":"user","content":"Say ok"}],"timeout":0.001}'After (b3bcd71)
rejection raised inside the route (files list, unknown after cursor)
curl -sS -w "\nHTTP %{http_code}\n" "http://127.0.0.1:50132/v1/files?after=file-doesnotexist" -H "Authorization: Bearer $LITELLM_MASTER_KEY"timeout (images, request timeout 0.001)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/images/generations -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","prompt":"a cat","timeout":0.001}'timeout (chat completions, request timeout 0.001)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/chat/completions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"gpt-5-mini","messages":[{"role":"user","content":"Say ok"}],"timeout":0.001}'The third pass covers the tails the second review round found untested live: a transcription session for a model the gateway does not serve, a WebRTC call whose client secret is real (issued by OpenAI for
gpt-realtime-2.1-minithrough the gateway) but whose SDP offer OpenAI rejects, and a pass-through route backed by the built-in Anthropic adapter, given a model the gateway does not serve. The token itself is not printed, only its lengthBefore (82e6b84)
realtime transcription session (unknown model)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/realtime/transcription_sessions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"input_audio_transcription":{"model":"no-such-transcribe"}}'realtime calls (real client secret, malformed SDP offer)
TOKEN=$(curl -sS -X POST http://127.0.0.1:$PORT/v1/realtime/client_secrets -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"session":{"type":"realtime","model":"gpt-realtime-2.1-mini"}}' | jq -r .value); echo "token length ${#TOKEN}"curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/v1/realtime/calls -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/sdp" --data-binary not-an-sdp-offeradapter pass-through (unknown model)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:$PORT/anthropic-adapter/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-model","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'After (b3bcd71)
realtime transcription session (unknown model)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/realtime/transcription_sessions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"input_audio_transcription":{"model":"no-such-transcribe"}}'realtime calls (real client secret, malformed SDP offer)
TOKEN=$(curl -sS -X POST http://127.0.0.1:50132/v1/realtime/client_secrets -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"session":{"type":"realtime","model":"gpt-realtime-2.1-mini"}}' | jq -r .value); echo "token length ${#TOKEN}"curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/v1/realtime/calls -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/sdp" --data-binary not-an-sdp-offeradapter pass-through (unknown model)
curl -sS -w "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:50132/anthropic-adapter/v1/messages -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d '{"model":"no-such-model","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'Observations the run turned up, none caused or worsened by this PR:
DELETE /v1/files/{id}answers the file object, neverdeleted: true/v1/messagesanswers 500api_errorfor a malformedmessagesfieldinternal_server_error(fix(proxy): name shared-handler errors by the status they answer #39555)Type
🐛 Bug Fix
Caveats (if any)
Medium
internal_server_errorLow
"None", split across fix(proxy): stop answering with the literal string "None" on the proxy-server, auth, and health routes #39540 and fix(proxy): stop answering with the literal string "None" on the spend and management routes #39542/v1/messagestail change is unobservable behind the Anthropic envelopeProxyExceptionwhere two re-raise it, lossless todaye2e_openai_endpointsflakes on the websocket multi-turn test, on staging tootest_responses_websocket_proxy_multi_turntimed out waiting for turn 2 at this tip, and failed the same way on two of today's four staging runstypestring wins as is,throttling_errorfrom a rate limit includedinternal_server_errorwhere OpenAI saysserver_error, the shared handler's naming is fix(proxy): name shared-handler errors by the status they answer #39555's jobManagedFileRepositoryon the module, the route builds it itselfosv-scanis red on dashboard lockfile advisories published today (next,sharp,js-yaml,vitest), cleared by build(deps): extend the diskcache osv suppression to 2026-10-09 to clear osv-scan #40309, not touched hereFinal Attestation