Repository navigation
chore(techdebt): clear fresh tech debt from the last 24 hours (rolling, 2026-09-06 to 2026-09-24) - #42710
Conversation
…026-09-06) Drop the TID251 cast import and both cast-ok casts from the refusal message_delta rebuild by narrowing the TypedDict union on its type literal, drop the redundant Mapping cast after the isinstance check in _mapping_field, and type the Lyria predict read-only helpers as Mapping[str, object] instead of a bare dict with mutable-ok. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…itellm_techdebt_20260906
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…itellm_techdebt_20260906
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…read out of the cleanup Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…itellm_techdebt_20260906
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>
…-16) 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>
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>
…0906 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/rust_bridge/lifecycle.py
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…0906 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/rust_bridge/legacy_callbacks.py
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… the cleanup 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>
…0906 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/router_strategy/complexity_router/jev_classifier.py # litellm/types/utils.py
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>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…22 changes 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>
…e literal Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
|
bugbot run |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…0923 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/proxy/litellm_pre_call_utils.py # litellm/proxy/utils.py
…lags as inert Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…litellm_techdebt_20260923 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/llms/anthropic/chat/guardrail_translation/handler.py # litellm/proxy/db/baseline_accounting.py # litellm/proxy/hooks/autorouter_baseline_cache.py
…the 24h window Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 similar comment
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
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 9d5f5f4. Configure here.
* refactor: clear fresh tech debt from the last 24 hours (2026-09-05, 2026-09-06) Drop the TID251 cast import and both cast-ok casts from the refusal message_delta rebuild by narrowing the TypedDict union on its type literal, drop the redundant Mapping cast after the isinstance check in _mapping_field, and type the Lyria predict read-only helpers as Mapping[str, object] instead of a bare dict with mutable-ok. * refactor: clear fresh tech debt from the last 24 hours (2026-09-09) * refactor: clear fresh tech debt from the last 24 hours (2026-09-10) * refactor: keep the pre-existing cost-estimate comment and usage cost read out of the cleanup * refactor: clear fresh tech debt from the last 24 hours (2026-09-13) * refactor: keep the model info pricing helper out of the cleanup * refactor: drop suppressions that no longer suppress anything (2026-09-16) * refactor: keep the rebind-ok reason inside the line limit * fix(anthropic): rebuild the refusal message_delta by spreading the chunk * refactor: clear fresh tech debt from the last 24 hours (2026-09-17) * refactor: clear fresh tech debt from the last 24 hours (2026-09-18) * refactor: clear fresh tech debt from the last 24 hours (2026-09-19) * refactor: keep the pre-existing protected-resource return type out of the cleanup * refactor: clear fresh tech debt from the last 24 hours (2026-09-20) * refactor: type fresh getattr, Any, and bare dict debt from 2026-09-22 * fix(mcp): keep the string guard on tools/list next_cursor * refactor(vercel_ai_gateway): type the embedding error headers dict * chore(techdebt): fix inert suppressions and missing Final in 2026-09-22 changes * chore(techdebt): shorten suppression reason to fit line length * chore(techdebt): format provider spread so its suppression sits on the literal * chore(techdebt): drop the logger extras suppression that LIT013 now flags as inert * chore(techdebt): clear fresh suppressions, Any aliases and slop from the 24h window * refactor(vercel): take a read-only headers mapping in get_error_class --------- Co-authored-by: mateo <mateo@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
TLDR
Problem this solves:
Anyand slop on mainmutable-ok, anAnyalias behind a pyright ignore, two coarsedictlocals, a narrating commentHow it solves it:
mutable-okon the refusalmessage_delta_ResponseDocSchemasas aTypeAliasoverdict[str, object]and drops the pyright ignoreFinal[dict[str, object]]User Flow
Refactor only, no user-visible behavior changes. Every run in this PR moves, removes or narrows annotations and comments; the few statements it touches resolve to the same value as before. The per-run notes below say where a run did more than that
Pre-Submission checklist
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
No runtime behavior is meant to change, so there is no before=fail leg to show. The after leg is a live proxy booted from bd480fd (worktree, port 43127, one
openai/gpt-4o-minideployment, real OpenAI calls) exercising the surfaces this run touched; 9d5f5f4 since then only retypes a parameter and wraps headers that every caller already passes ashttpx.HeadersThe
/openapi.jsonlisting shows the retyped_ResponseDocSchemasstill feeds fastapi'sresponses=on every Responses route. The static claim is the per-file count frompython3 scripts/check_type_discipline.py <file>at the merge base with main (09ebb28) versus bd480fd for the files this run touched:litellm/passthrough/main.py45 to 44,litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py215 to 214,litellm/proxy/response_api_endpoints/endpoints.py86 to 85 (line 52 no longer reported),litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py67 to 67 with the LIT013 report on line 1040 gone,litellm/proxy/utils.py636 to 636.make lintandmake checkpass on the tip, and the 12 mapped test files pass locallyFindings by run date
2026-09-24 run (window 2026-09-23 07:43 UTC to 2026-09-24 07:43 UTC, 106 commits)
litellm/proxy/response_api_endpoints/endpoints.py:52introduced_ResponseDocSchemas = dict[int | str, dict[str, Any]]behind# pyright: ignore[reportExplicitAny], which LIT010 also reported as an unannotated alias. It is now_ResponseDocSchemas: TypeAlias = dict[int | str, dict[str, object]]; fastapi'sresponseskwarg accepts it unchanged and the ignore is gonelitellm/passthrough/main.py:543introduced_streaming_request_data: dict. It only feedsis_streaming_request, so it isFinal[dict[str, object]]litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py:3905introduceddata: dict[str, Any]with a prose comment standing in for a suppression._read_request_bodyreturns a string-keyed dict and every later store narrows onisinstance, so it isFinal[dict[str, object]]and the comment is gonelitellm/proxy/utils.py:2392added# Execute guardrail pipelines before the normal callback loop, which restates theif not skip_guardrailsline under it. Deleted# mutable-okonrefusal_delta: Final[MessageDelta]instreaming_iterator.py:1040, carried from refactor: clear fresh tech debt from the last 24 hours (2026-09-06, 2026-09-09, 2026-09-10, 2026-09-13, 2026-09-16, 2026-09-17, 2026-09-18, 2026-09-19, 2026-09-20, 2026-09-22) #40030, is aFinalTypedDict literal, so LIT013 on current main reports it as inert and the gate failed. Deletedget_error_classinlitellm/llms/vercel_ai_gateway/embedding/transformation.pytoheaders: dict[str, str] | httpx.Headersadded a LIT001 the base did not have (29 to 30). It is nowMapping[str, str] | httpx.Headers, the same shape as the Mistral configs, and the value is normalized tohttpx.Headersbefore it reachesBaseLLMException, which is what every caller inllm_http_handler.pyalready passesDeliberately left alone:
pending = asyncio.ensure_future(...)insse_keepalive.py(1857f5d) andrevoked = Falseinsession_endpoints.py(bc3b5b1) are genuinely rebound, so the honest fix is a restructure rather than arebind-ok._apply_v3(request_data: dict)instraiker.py(76b0b10) forwards to_build_envelope,_fail,_blockand_record, which all take and mutate a baredict, so narrowing one signature needs the whole v3 path retyped._normalize_response_format(value: Any)in the TwelveLabs Pegasus transformation (2471602) and the baredictparams on_merge_pipeline_metadata_bucket/_merge_pipeline_metadata_writesinproxy/utils.py(996019c) are the same shape. Thegetattrreads instraiker.py,types_utils/utils.pyandrust_bridge/secret_manager.pyresolve dynamic attribute names. The new multi-line comments in otel, ollama, bedrock responses, mcp_server and the UI session endpoints explain contracts rather than restating code. No budget ceiling was raised (the only budget change addsLIT013at limit 0), notype: ignorelanded, no bare TODO or FIXME landed2026-09-23 run (window 2026-09-22 07:40 UTC to 2026-09-23 07:40 UTC, 133 commits)
fc00554 feat: add configurable provider affinity header mapping (#41033):
litellm/litellm_core_utils/provider_affinity.pycarried a# mutable-okon thedef add_provider_affinity_header(line, which suppresses nothing; the return annotation and return statements already carry their own. Removed the inert onecb2f225 feat(proxy): opt-in include_guardrail_response (#42462):
litellm/proxy/litellm_pre_call_utils.pyput# rebind-okon the] = (continuation line of a subscript assignment, where LIT011 cannot see it. Flattened the target onto one line so the suppression sits on the statement989d7b8 fix(proxy): attribute provider and model_info on pre_call_hook rejections (#42079):
litellm/proxy/utils.pyhad a# mutable-okonlitellm_params["metadata"] = {}, which is a parameter mutation (LIT011) as well as a mutable literal (LIT002), so it now carries# rebind-okfor the store and# mutable-okfor the seeded literal. The same commit added a comment-only# mutable-okline above a**({...} if ... else {})spread; that line covered nothing, so it is gone and the suppression sits on the two literals insteadb0ac23d feat(logger): dispatch Python logging through the Rust diagnostics processor (#42440):
litellm/rust_bridge/logger.pyplaced# mutable-okon the closing},of theextra={...}dict, where LIT002 does not look. Moved it to theextra={line at first; main has since shipped LIT013, which reports that trailer as inert, so 5364e9f removes it entirely7056151 fix(gemini): simplify model version check (#42465):
litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.pyintroducedmodel_name = ...withoutFinalwhile the neighbouring local had it. AddedFinalb673ee6 feat(arize): per-team success and error sampling rates (#42447):
litellm/integrations/arize/arize.pyintroducedrate = float(value)withoutFinal. AddedFinalLeft alone on purpose:
# rebind-okondata[...]inlitellm_pre_call_utils.pyis genuinely required because pre-call hooks share one request dict, and the# mutable-okondict(single_deployment.get("model_info"))inproxy/utils.pyis required because the router reads a mutable model-info payload. NewAnyand baredictusage inlitellm/proxy/_experimental/mcp_server/bridge_token_flow.py,litellm/experimental_mcp_client/client.pyand the rust fixture generator are behavior-adjacent and too invasive for a same-day cleanupAbsorbed from #40030 on 2026-09-24
Absorbed from #40030 on 2026-09-24: its full diff re-applied on this branch via merge 75bcfaf. Dropped while absorbing: its
type-discipline-budget.jsonedits (main's ratchet already carries them), itsrebind-okmoves inbaseline_accounting.pyandautorouter_baseline_cache.pywhere main already had the same placement, and themutable-okonrefusal_deltaas described above2026-09-22 (window 2026-09-21 07:43 UTC to 2026-09-22 07:43 UTC, 339 commits, 1,186 files)
refactor(types): replace Any with proven types in 32 files:initialize_mavvrik_focus_export_jobinlitellm/integrations/mavvrik_focus/mavvrik_focus_logger.pygained a one-use_PodLockManagerProtocol wrappinggetattr(proxy_logging_obj, "db_spend_update_writer", None)andgetattr(writer, "pod_lock_manager", None).proxy_logging_objis declaredProxyLogging, whose__init__always assignsdb_spend_update_writer: DBSpendUpdateWriter, which always assignspod_lock_manager: PodLockManager, so the lock manager is now read directly under aFinaland the Protocol is deletedfeat(cost): warn and count $0 cost on billable requests:_audio_tokens(details: object)inlitellm/litellm_core_utils/llm_cost_calc/zero_cost_diagnostic.pyreadgetattr(details, "audio_tokens", None). Its only two callers passUsage.prompt_tokens_detailsandUsage.completion_tokens_details, both declared wrappers with anaudio_tokens: int | Nonefield, so the parameter isPromptTokensDetailsWrapper | CompletionTokensDetailsWrapper | Noneand the read is a plain attribute read behind aNonecheckget_error_classinlitellm/llms/vercel_ai_gateway/embedding/transformation.pywas rewritten withheaders: Any. The baseBaseConfig.get_error_classdeclaresheaders: dict | httpx.Headers; the override takesdict[str, str] | httpx.Headers, which is whathttpx.Response.headerscarries and whatVercelAIGatewayExceptionaccepts.httpxwas already importedfix(proxy): release unclaimed budget reservations at request end:budget_reservation_from_metadatainlitellm/litellm_core_utils/core_helpers.pyreturned a baredict | None. The reservation is a string-keyed payload, so it isdict[str, object] | None; its callers inproxy_track_cost_callback.pyacceptdict | NoneunchangedFollow-up on the 2026-09-20
next_cursoritem: CI'stest_mcp_client_unit.py::test_list_toolsfeeds aMagicMockresult, whose truthy mock cursor sent the walk into a second page and an empty list. 018a6a9 keeps the typedresult.next_cursorread and restores theisinstance(next_cursor, str)guard so a non-string cursor still ends the walk, matching the pre-cleanup behaviorDeliberately left alone: the
**kwargs: Anyand the sevennoqa: PLC0415inlitellm/proxy/_experimental/mcp_server/operations.py(1098604) are lines moved out ofserver.py, not new code._get_config_value(...) -> Any | Noneingeneric_guardrail_api/__init__.py(8795be0) reads an arbitrary attribute by name, so a narrower type needs a typed config model, not a one-line change.should_report_buginbug_report.pyreadsstatus_codeoff aBaseExceptionthat does not declare it. The section-header comments inlitellm/litellm_core_utils/tokenizer.py(0abd926) follow a pattern used in several hundred places in the tree, so removing five is taste. The####banner inuser_api_key_auth_mcp.py(82eef2f) matches the sibling banners in the same function. The multi-line comments added in the TinyFish, MCP OAuth, parallel-request-limiter and pass-through timeout code document billing, authorization and timeout contracts rather than restating code. No budget ceiling was raised in the window, notype: ignorewas added, and no bare TODO or FIXME landed2026-09-20 (window 2026-09-19 07:43 UTC to 2026-09-20 07:43 UTC, 205 commits, 1,118 files)
Suppression trailers that suppress nothing.
scripts/check_type_discipline.pyhonours a*-oktrailer only on the exact line it reports, which for a multi-line statement is the first line. Each trailer below sat on the closing line, so the violation it meant to justify still counted againsttype-discipline-budget.jsonwhile the comment did nothingfix(proxy): estimate auto-router baseline costs from durable cache history:# mutable-ok: spend-log JSON serialization requires plain mappingson the closing}of theautorouter_savings_estimateliteral inlitellm/litellm_core_utils/litellm_logging.py. Moved onto the opening{the gate reports# rebind-ok: keyset pagination advances after each complete timestamp groupon thecursor = page[-1].started_atrebinding inlitellm/proxy/db/baseline_accounting.py, while LIT010 fires on thecursor: float | None = Nonedeclaration. Moved onto the declaration# rebind-ok: drain under lockon the closing]of theclient.baseline_accounting_transactions = ...[32:]slice inbaseline_accounting.py. Moved onto the assignment line# noqa: BLE001 # unknown acknowledgements can be replayed safelyonexcept (Exception, asyncio.CancelledError) as error:inbaseline_accounting.py. Ruff does not raise BLE001 there because the handler re-raises, so the trailer is inert. Deleted# rebind-ok: request-owned retry markerand# rebind-ok: capture uncertainty for failure loggingon the closing lines of the twologging_obj.*assignments inlitellm/proxy/hooks/autorouter_baseline_cache.py. Moved onto the assignment lines; the second reason is shortened touncertain capture for failure logsso the line fits the 120-column formatterrefactor(auth): resolve org identity through an auth_checks helper:# noqa: BLE001 # only a DB outage may fail auth here, ...onexcept Exception as e:inlitellm/proxy/auth/auth_checks.py. Ruff exempts the handler because it logs withexc_info=True, so the trailer is inert. Deletedfeat(guardrails): add headroom guardrail for message compression:_hoisted_top_level_system_message(self, data: dict)inlitellm/llms/anthropic/chat/guardrail_translation/handler.pyonly callsdata.get, and its# mutable-ok: API message payloadsat on the return-type line where LIT001 never looks. Typed asMapping[str, object]and the trailer deletedrefactor(mcp): port MCP client and server helpers to MCP SDK 2:next_cursor = getattr(result, "next_cursor", None)plus anisinstance(next_cursor, str)guard inlitellm/experimental_mcp_client/tools.py.mcp.types.ListToolsResult.next_cursoris declaredstr | Noneon the pinnedmcp>=2.2.0, so the read isresult.next_cursorand the guard isif not next_cursorNet effect on the gates at the tip: type-discipline violations in the six touched files drop from 1057 to 1051, basedpyright errors in them from 2070 to 2065, ruff-strict unchanged at 309. No budget file was edited (the working-tree counts sit under every ceiling and
AGENTS.mdasks not to touch budgets on a PR branch)Deliberately left alone:
video_tokens=getattr(response_api_usage.input_tokens_details, "video_tokens", None)inlitellm/responses/utils.pymatches nine siblinggetattrreads that predate the window, so swapping one line would be a drive-by.tool_call_map: Final[dict[_ToolCallKey, dict[str, Any]]]instreaming_chunk_builder_utils.pyandextract_file_data(cast(Any, ...))infiles_endpoints.pyneed a typed tool-call payload model, not a one-line change. The MCPgetattr/setattrreads inmcp_server/utils.py,sampling_handler.pyandelicitation_handler.pydeliberately accept both mapping and SDK-object shapes, so direct access would change what a mapping result does.prisma_client: Final[PrismaClient] = getattr(managed_files_obj, "prisma_client")reads a fieldBaseFileEndpointsdoes not declare. Thegetattr(_native, ...)lookups inrust_bridge/fork_guard.pyprobe an optional native extension. The new multi-line comments inencrypt_decrypt_utils.py,experimental_mcp_client/client.py,proxy_cli.pyand the OpenAI Responses guardrail handler explain compatibility or forking contracts. No budget ceiling was raised in the window, notype: ignorewas added, and no bare TODO or FIXME landed2026-09-19 (window 2026-09-18 07:43 UTC to 2026-09-19 07:43 UTC, 245 commits, 881 files)
fix(a2a): Entra credentials own the chat route bearer over a stored api_key or authorization header:_registry_headersinlitellm/llms/a2a/chat/transformation.pyreturneddict[str, Any] | Nonefor a filtered copy of aMapping[str, object]. Typed asdict[str, object] | Nonefix(proxy): unpin cost-map pricing copied into model_info and report pricing overrides:echoed_cost_map_pricing_fieldsandpricing_override_fieldsinlitellm/types/utils.pytookMapping[str, Any]but only read keys and compare values toNone. Typed asMapping[str, object]fix(policy_engine): deliver guardrail text rewrites on multi-choice, unfinished, and envelope-less streams:_spread_text_rewrite_over_stream_eventsinlitellm/llms/openai/responses/guardrail_translation/handler.pytookstream_events: Sequence[Any]and only passes each event tostream_item_field(item: object, ...). Typed asSequence[object]fix: strip eager_input_streaming for non-Claude providers next to input_examples:_without_anthropic_only_tool_keys(tool: dict) -> dictand_drop_anthropic_only_tool_keys(tools: list[dict] | None)inlitellm/main.pywere bare. Typed asdict[str, object]andlist[dict[str, object]]fix(proxy): exchange role default, logged rejections, gated grant listing: the rewritten_build_aggregate_authorization_server_responsesignature inlitellm/proxy/_experimental/mcp_server/discoverable_endpoints.pykept a bare-> dict. Typed asdict[str, object]; the sibling_build_aggregate_protected_resource_response -> dictpredates the window and is left alonefeat(websearch): let the model emit objective + multi-query search shape for providers that support it:tool_args: dict | Noneinlitellm/integrations/websearch_interception/handler.py. Typed asdict[str, object] | None, keeping itsmutable-okfeat(proxy): add LiteLLM_DailyGlobalSpend key-free rollup for the usage dashboard:pending_daysinlitellm/proxy/spend_tracking/daily_global_spend_rollup.pyhad no caller inlitellm/,enterprise/,tests/ordocs/. DeletedDeliberately left alone:
generation_config: dict[str, Any] | Noneinlitellm/llms/gemini/interactions/transformation.py, because the poppedimage_configis spread into a dict and typing it asobjectneeds anisinstanceguard that would change what a non-dict value does. Thegetattrreads onincomplete_details,fallback_headers_adopted,param_valueandguardrail_nameread fields their declared types (ModelResponse, a response union, anAnyconfig row,object) do not carry. TheLoggingSurfacegetattr/setattron private_defer_async_loggingand_native_pending_logginginlegacy_callbacks.pyis the Rust bridge's adapter contract, not a same-day cleanup. Every newnoqa,pyright: ignore,mutable-okandrebind-okin the window names one rule and carries a reason. No budget ceiling was raised, notype: ignore, and no bare TODO or FIXME was added. Merging main also dropped the 2026-09-18 finding 1: main rewrotelegacy_callbacks.pyandcallbacks_neededwith itslitellm_request_debugread no longer exists, so the branch now carries main's version of that file unchanged2026-09-18 (window 2026-09-17 07:43 UTC to 2026-09-18 07:43 UTC, 149 commits, 680 files)
refactor(rust): run OCR through a route-neutral callback contract and a legacy Logging adapter:callbacks_neededinlitellm/rust_bridge/legacy_callbacks.pyreadgetattr(logger, "litellm_request_debug", False).Logging.litellm_request_debugis declared with aFalsedefault (the 2026-09-13 run fixed the same read inlifecycle.pybefore it moved here), so it is a plain attribute readadd PublicDispatchand c01db25bring x-litellm-rust:RequestT,NativeT,ResultTinlitellm/rust_bridge/dispatch.pyandResultTinlitellm/rust_bridge/response_metadata.pywere module-levelTypeVars without: Final(LIT010). Annotatedfix(router): validate Jev classifier probabilities:NativeHookindispatch.pyandJevProbabilityinlitellm/router_strategy/complexity_router/jev_classifier.pywere implicit aliases assigned at module level. Declared with: TypeAliasfeat(management_v1): bulk update team member budgets:member_budget_patchinlitellm/proxy/management_endpoints/common_utils.pyreturneddict[str, Any],_upsert_budget_and_membershiptookbudget_patch: dict[str, Any], and itssourceandcreate_datalocals wereAny-valued. The patch is only read, so it isMapping[str, object]on both ends and the locals areobject-valued; themutable-okoncreate_datastays because the Prisma payload is still built as a dictDeliberately left alone: the
dict[str, object]parameters onMetadataUpdaterandfinalizeinlegacy_callbacks.py, becauseupdate_response_metadatawrites into that kwargs dict, soMappingwould misdescribe the contract. Thegetattrreads inlitellm_logging.py(_hidden_params),ocr/main.py(file_input.name),rust_bridge/ocr/route_host.py(error.headers,ocr_request_format_error) andmodel_management_endpoints.py(iterated field names) read fields their declared types do not carry. Therequested_query_params: dict | Nonerebinding inpass_through_endpoints.pyis pre-existing legacy flow that the window only extended. The multi-line crash-recovery comment inautoroute/commands.pyand the inherited-window comment incommon_utils.pyexplain non-obvious behavior rather than restating code. Every newpyright: ignorein the window names one rule and carries a reason. No budget ceiling was raised in the window, notype: ignore, and no bare TODO or FIXME was added2026-09-17 (window 2026-09-16 04:19 UTC to 2026-09-17 07:43 UTC, 372 commits since the first-parent base a8979fe)
Every suppression added in the window (98 in non-test files) was stripped and the matching gate re-run on its file, as the 2026-09-16 run did. Two produced no violation without their comment; the other 96 reproduce their violation and stay. All seven new
pyright: ignoretrailers list exactly the one rule that fires, so nothing to narrowfix(proxy): let the OTel trace id fallback fill a null litellm_trace_id:# rebind-ok: metadata is the request's own out-param dictonmetadata["trace_id"] = trace_idinlitellm/proxy/litellm_pre_call_utils.pysuppressed nothing (LIT011 only flags thedata[...]store two lines up, whose marker stays). Removedfix(mcp): preserve browser OAuth for unrelated bearer tokens: inlitellm/proxy/_experimental/mcp_server/bridge_token_flow.pythe# noqa: PLC0415 # envelope imports bridge typessat on theis_envelope,name inside a parenthesized import, but ruff reports PLC0415 on thefromline, so the import was unsuppressed. Moved the trailer onto thefromlinefix(guardrails): don't add post_call output scan for MCP-only Presidio modes:initialize_presidioinlitellm/proxy/guardrails/guardrail_initializers.pyreadgetattr(litellm_params, "presidio_filter_scope", None).LitellmParamsinheritsPresidioConfigModel, which declares the field with aNonedefault, so it is a plain attribute readfix(proxy): resolve rate-limit fallbacks after model normalization and retry from a client-request snapshot:_resolve_fallback_modelsinlitellm/proxy/common_request_processing.pyreturned barelist | None;get_fallback_model_groupalready types the chain aslist[str] | None, so the return is nowlist[str] | None. Thefallbacks: listparameter stays becauseget_fallback_model_groupitself takeslist[Any]Deliberately left alone: the
getattr(chunk, "choices", ())added by 884e969 instreaming_chunk_builder_utils.pylooks redundant against its declaredModelResponse | ModelResponseStreamunion, buttest_stream_chunk_builder_tolerates_trailing_chunk_without_choicesfeeds aBaseLiteLLMOpenAIResponseObjectwith nochoices, so the annotation is narrower than the runtime input and thegetattris load-bearing; widening the annotation is a follow-up for that module. Also left: the# Check RBACand router cooldown comments and thedata: Anyinsafe_json_dumps.py, which the diff shows as added but are moved lines that already existed at the base; the bareSequenceinprometheus._set_customer_budget_metricsand thedictparams ingen_ai_semconvandconverse_transformation, which mirror the untyped shapes they receive; the threedata: dictseeds inproxy_server.py, which are rebound in legacy handlers. No budget ceiling moved, notype: ignore, and the only new TODO links LIT-57222026-09-16 (window 2026-09-15 07:43 UTC to 2026-09-16 07:43 UTC, 173 commits)
Every suppression added in the window was stripped one at a time and the matching gate re-run on the file (
scripts/check_type_discipline.py,ruff check --config ruff-strict.toml,basedpyrightwith thetype_check_gate.pyinvocation). The ones below produced no violation without their comment, so the comment was a dead marker and is removed. Every other suppression in the window (164, including everynoqa) reproduces its violation when stripped and staysfix(guardrails): scan the Anthropic top-level system prompt and tool_use arguments: eight# mutable-ok: guardrails rewrite the caller's request payload in placetrailers inlitellm/llms/anthropic/chat/guardrail_translation/handler.pysat on item stores (block["input"] = ...,content[i]["text"] = ...) that LIT001/LIT002 never flag, and one on a list comprehension bound toFinal. Removed;ruff formatcollapsed the parenthesized continuations back to single linesfeat(proxy): add /nvidia_nim passthrough route for NIM object detection and OCR /v1/infer: the# mutable-okon the returned dict literal inlitellm/llms/nvidia_nim/passthrough/transformation.py(line 113) suppressed nothing. Removed; the two on the signature and thedict(headers)call are needed and stayfeat(proxy): add POST /user/bulk_new for batched user and team membership creation: inlitellm/proxy/management_helpers/bulk_user_creation.pya# pyright: ignore[reportUnknownArgumentType]on the_set_object_permissioncall and a# mutable-okon a list literal already wrapped intuple(...)were dead. Removed. The ignore onteam.metadatalistedreportUnknownArgumentTypealongsidereportUnknownMemberType; only the latter fires, so the code list is narrowedfeat(proxy): add POST /user/bulk_delete and POST /team/bulk_member_delete:# pyright: ignore[reportUnknownArgumentType] # HTTPException.detail is untypedinlitellm/proxy/management_helpers/bulk_user_deletion.py(line 190) sat onstr(exc.detail), which type-checks. Removed; the neighbouring ignore two lines up still fires and staysfeat(router): add Switchyard capability classifier: inlitellm/router_strategy/complexity_router/complexity_router.pya# mutable-okon a dict literal already annotatedFinal[StandardLoggingRoutingDecision](which LIT002 exempts) and one on a.append(...)call (method calls are out of the checker's reach) were dead. Removedfix(responses): build the billed terminal response immutably and guard the cache dump: the ignore inlitellm/responses/streaming_iterator.pylistedreportUnknownArgumentType, reportArgumentType; onlyreportArgumentTypefires, so the list is narrowed# rebind-ok: the counter update reads the stamp off the shared dictonbudget_reservation["finalized"] = Trueinlitellm/proxy/hooks/proxy_track_cost_callback.pydescribed a read, while the marker suppresses an in-place parameter store. Reworded tostamps the caller's shared dict for the counter update_with_refusal_stop_details(cdea9c9) readprocessed_chunk["usage"]on aNotRequiredkey, which raisedKeyErrorfor amessage_deltawithoutusageand pushedreportTypedDictNotRequiredAccesstwo over its ceiling on this branch (main sits exactly at the limit). The chunk is now rebuilt asrefusal_chunk: Final[MessageBlockDelta] = {**processed_chunk, "delta": refusal_delta}, which is what the pre-rewrite code did with acast, sousageandcontext_managementpass through whether or not they are presentDeliberately left alone: the
dict[str, Any]/Mapping[str, Any]parameters acrosslitellm/proxy/guardrails/guardrail_hooks/singulr/singulr.pyandlitellm/llms/openai/chat/guardrail_translation/handler.py(raw request payloads with no typed shape to read through), and thegetattrreads inexception_mapping_utils.py,reset_budget_job.pyand the pass-through handlers, which read fields the declared types do not carry. No budget ceiling was raised in the window, notype: ignore, and no bare TODO or FIXME was added2026-09-13 (window 2026-09-12 07:43 UTC to 2026-09-13 07:43 UTC, 65 commits)
fix(cost): bill request-level OCR pricing on direct SDK calls:_deployment_model_infoand_ocr_model_infoinlitellm/cost_calculator.pyreadgetattr(litellm_logging_obj, "litellm_params", None).Logging.__init__always assignslitellm_params, so both are plain attribute reads now; theNoneguard on the logging object stays. Because the field is a non-optional dict, the follow-up check isif not litellm_params, which returnsNonefor the same inputs (an empty dict never yielded amodel_infoanyway)fix(mcp): require admission for delegated OAuth:litellm/proxy/_experimental/mcp_server/mcp_server_manager.pyfiltered the registry withgetattr(server, "auth_type", None).MCPServer.auth_typeis a declared field, so it isserver.auth_typenowrefactor(ocr): complete native lifecycle and preserve Azure auth:callbacks_neededinlitellm/rust_bridge/lifecycle.pyreadgetattr(logger, "litellm_request_debug", False).Logging.litellm_request_debugis declared with aFalsedefault, so it is a plain readfix(responses): preserve hosted web search calls:_web_search_calls_by_call_idinlitellm/responses/litellm_completion_transformation/transformation.pyreadgetattr(choice.message, "provider_specific_fields", None).Message.provider_specific_fieldsis declared, so it is a plain read; theisinstance(..., Mapping)guard still handlesNonefix(model_management): stop persisting cost map pricing as a deployment override:without_server_derived_pricinginlitellm/types/utils.pytakesMapping[str, Any]. Started as finding 5 and reverted (1bc2438): theobjectsignature forces the/model/newcaller to buildModelInfothroughmodel_validateinstead of**unpacking, which reviewers cannot verify at a glance, so it stays as reported debtAlso committed:
ui/litellm-dashboard/src/lib/http/schema.d.tsloses two stalesoft_budgetdoc lines. That is the generator output for the merged tip (the docstring was removed on the base branch without regenerating), andmake checkfails until it is in the treeDeliberately left alone:
getattr(logger, "logger_fn", None)in the samecallbacks_neededfunction andgetattr(logging_obj, "call_type", None)plusgetattr(logging_obj, "_native_pending_logging", None)incommon_request_processing.py(347b642):logger_fnis not declared onLogging,logging_objis typedAnyon a pre-existing signature, and_native_pending_loggingis set from Rust (litellm-rust/crates/python-bridge/src/lifecycle/bindings.rs), so there is no static type to read through.getattr(updated_patch.litellm_params, field)inmodel_management_endpoints.py(76cb0fe) iterates over field names, so the string access is inherent.getattr(event, "output_index", None)andgetattr(tool_call, "id", None)in the Responses bridge (eddfb5f) read fields the baseobject/BaseLiteLLMOpenAIResponseObjecttypes do not declare. Themutable-okon the web search call index, therebind-okon the OCR release signal and the tworeportPrivateUsageignores inlifecycle.pyall carry reasons and have no static replacement. The multi-line OAuth and admission comments added inmcp_server/server.py,budget_reservation.py,key_management_endpoints.py,user_api_key_cache.pyandproxy_cli.pydocument security and rollout contracts rather than restating code. The# Run the coroutine in its original contextcomment inlogging_worker.pypredates the window (only re-indented)..github/scripts/*.pygainedAnybut sits outside the typedlitellm/tree. No budget ceiling was raised in the window, notype: ignore, and no bare TODO/FIXME was added2026-09-10 (window 2026-09-09 07:17 UTC to 2026-09-10 07:43 UTC, 225 commits)
fix(anthropic): replay OpenAI encrypted reasoning byte for byte behind /v1/messages:_thinking_block_from_reasoning_itemand_assistant_group_to_input_itemsinlitellm/llms/anthropic/experimental_pass_through/responses_adapters/transformation.pyreturneddict[str, Any]. The values are only ever serialized, so both are nowdict[str, object]; themutable-oktrailers stay because the payloads are still built as dictsfix(streaming_handler): replay a cached completion with no choices as an empty stream:litellm/litellm_core_utils/streaming_handler.pyreadgetattr(cached_choice.delta, "tool_calls", None).Delta.tool_callsis a declared field, so it is a plain attribute read nowfix(router): pin bridge-replayed encrypted reasoning to the deployment that minted it:_anthropic_content_blocksinlitellm/router_utils/pre_call_checks/encrypted_content_affinity_check.pyre-implemented the message walk that_anthropic_content_listsinlitellm/litellm_core_utils/prompt_templates/common_utils.pyalready does (2bca7ff). The helper is now public asanthropic_content_listsand the router check iterates through it; the module already imported fromcommon_utils, so no new import edgeDeliberately left alone: three candidates were started and then reverted (2d0ee09) once
git log -Sshowed they were pre-existing lines only re-indented or moved in the window, so out of scope:getattr(response_obj, "usage", None)(249a999) andgetattr(usage_obj, "cost", None)(9e25dd7) inlitellm/responses/streaming_iterator.py, and the# Use completion_costcomment inlitellm/proxy/management_endpoints/cost_tracking_settings.py. Theusageread also cannot become a plain attribute without breakingtest_responses_streaming_does_not_reset_prior_completion_start_time, which drives the iterator withMock(spec=ResponsesAPIResponse). The remainingmutable-okandcast-oktrailers added in the window sit on untyped client JSON or on API message payloads that are built as dicts and have no static replacement. No budget ceiling was raised in the window and no bare TODO/FIXME ortype: ignorewas added2026-09-09 (window 2026-09-08 07:43 UTC to 2026-09-09 07:43 UTC, 87 commits)
feat(otel): typed semconv-aligned OpenTelemetry instrumentation:_maybe_construct_otel_v2inlitellm/litellm_core_utils/litellm_logging.pyusedgetattr(callback, "callback_name", None)right afterisinstance(callback, OpenTelemetryV2).OpenTelemetryV2.__init__always setscallback_name, so it is now a plain attribute readfeat(mcp): add schema discovery proxy mode:_mcp_proxy_identityinlitellm/proxy/_experimental/mcp_server/tool_search.pycarried# mutable-ok: absent metadata defaulton(tool.meta or {}).get(...). NowNone if tool.meta is None else tool.meta.get(...), same result forNoneand for an empty mapping# mutable-ok: TypedDict identity payload. It is now assigned toresolved: Final[MCPProxyToolIdentity]and returned, which the LIT001 gate recognizes as a TypedDict build without a suppressionfeat(bedrock): add TwelveLabs Marengo Embed 3.0 embeddings:litellm/types/llms/bedrock.pygained a# TwelveLabs Marengo Embed typessection header above two aliases whose names already say that. DeletedDeliberately left alone:
_group_deploymentsinlitellm/router_strategy/complexity_router/complexity_router.py(0175c7d) readsget_model_listthroughgetattrbecause the existing tests inject fake routers without that method, so a direct call fails them.attribute_ofinlitellm/proxy/common_utils/openai_error_payload.pyand thegetattr(guardrail_to_apply, name, default)inunified_guardrail.pytake dynamic attribute names, so there is no static replacement. Therebind-okloop accumulators inlitellm/batches/batch_utils.pyneed a functional rewrite of the whole loop to remove, which is not a same-day cleanup. Thecast-okcasts inlitellm/proxy/utils.pypreserve the legacy callback tuple contract. No budget ceilings were raised in the window (every limit moved down) and no bare TODO/FIXME ortype: ignorewas added2026-09-06 (window 2026-09-05 07:43 UTC to 2026-09-06 07:43 UTC, 108 commits)
fix(anthropic): satisfy lint budget gates for refusal translation:litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.pyimportedcastunder# noqa: TID251and used twocast-okcasts in_with_refusal_stop_detailsto rebuild the refusalmessage_delta. Replaced withprocessed_chunk["type"] != "message_delta"narrowing, aMessageDeltatyped delta, and aFinal[MessageBlockDelta]spread of the chunk with the new delta (the explicitMessageBlockDelta(...)constructor from the first pass readusageunconditionally; see 2026-09-16 finding 8). Thecastimport is gone from the filefix(anthropic): stream the refusal text on bridged /v1/messages calls:_mapping_fieldinlitellm/llms/anthropic/experimental_pass_through/messages/utils.pycastcontainertoMapping[str, object]right afterisinstance(container, Mapping). Dropped the cast;container.get(key)type-checks as isstyle(vertex-ai): satisfy Lyria quality gates:_is_audio_predict_responseand_get_audio_prediction_countinlitellm/proxy/pass_through_endpoints/llm_provider_handlers/vertex_passthrough_logging_handler.pytookjson_response: dict # mutable-okwhile only reading it. NowMapping[str, object], no suppression._handle_audio_predict_responsekeepsdictbecause it stores the response intoStandardPassThroughResponseObjectDeliberately left alone: the repeated
is_non_conversational_call_type(...) and not self.inspect_embeddingsskip in the AIM and Cato hooks (6e05ac5): the predicate is already shared, a wrapper would save one line per hook.truncate_base64_in_messages_async'sstr | list | dict | Nonemirrors the sync helper's contract.getattr(mcp_server, "per_server_oauth_discovery", False)follows the surrounding pattern for the loosely typed server object. No budget ceilings were raised in the window (every limit moved down)Type
🧹 Refactoring
Caveats (if any)
Low
2026-09-16 finding 8 is a behavior fix inside this branch's own earlier commit, not a cleanup of main; without it a refusal
message_deltamissingusageraisedKeyErrordict[str, object]on the two passthrough dicts is still LIT001-reported, same count as the baredictit replacesThe removed
pyright: ignore[reportUnknownArgumentType]inbulk_user_creation.py:351andbulk_user_deletion.py:207fire under the dev.venvbasedpyright but not under.venv-typecheck, which is the interpreterscripts/type_check_gate.pyand CI run; the gate env is the one the repo measures against, so they stay removedStatic-count claims in this body are measured with
.venv-typecheckandscripts/check_type_discipline.py; a dev.venvwith different stub versions reports different per-file totalsGreptile scored the 2026-09-23 head 5/5 and has not answered any
@greptileaitrigger or the draft/ready toggle since; on 2026-09-24 it also ignored the same trigger on fix(proxy): list key and team model aliases in GET /v1/models #42908, build: drop psycopg-binary from extra_proxy and use the image libpq #42909, feat(fips): build grpcio from source against system OpenSSL and drop its scanner waiver #42912 and feat(otel v2): tri-state otel_span_scope (full, no_internal, llm_only) for every tenant OTEL destination #42610, so this is a repo-wide bot outage, not a skipped review. Bugbot is clean at 9d5f5f4 and every commit since the scored head is a cleanup edit listed aboveAdversarial caveat pass at bd480fd raised one medium finding, the vercel LIT001, fixed in 9d5f5f4. It also flagged the missing live after leg, now pasted above, and two dev-venv basedpyright differences, rebutted with the gate env above
Final Attestation
Link to Devin session: https://app.devin.ai/sessions/6bd2a65cef194d41924fa36d6a940997
Open in Devin Desktop: https://app.devin.ai/desktop/session/6bd2a65cef194d41924fa36d6a940997?variant=devin