Repository navigation
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
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>
🤖 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:
|
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
bugbot run |
…itellm_techdebt_20260906
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
bugbot run |
…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>
|
bugbot run |
…itellm_techdebt_20260906
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
…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>
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
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 018a6a9. Configure here.
|
Superseded by #42710, which now carries this cleanup: every run from 2026-09-06 to 2026-09-22 was merged onto that rolling branch |
TLDR
Problem this solves:
castimport, twocast-okcasts, twomutable-okbaredictparamsgetattron an already narrowed type, twomutable-ok, one section-header commentdict[str, Any]returns, agetattron a declaredDeltafield, a copy of the Anthropic content traversal that already lives incommon_utilsgetattrreads on declared fieldsrebind-okreasonrebind-ok, anoqaon the wrong line, agetattron a declared field, a barelistreturngetattron a declared field, four unannotated moduleTypeVars, two implicit type aliases, fourAnyannotations in the member budget helperAnyannotations, four baredict/listsignatures, one dead public functionnoqa: BLE001that suppress nothing, a baredictparam, agetattron a declared SDK fieldgetattrreads on declared fields behind a one-useProtocol, oneheaders: Any, one baredictreturnHow it solves it:
ContentBlockDelta | MessageBlockDeltaunion on itstypeliteral and rebuild the refusal chunk by spreading itMappingcast that followed anisinstance(container, Mapping)checkMapping[str, object]callback.callback_namedirectly after theisinstance(callback, OpenTelemetryV2)checkFinal[MCPProxyToolIdentity]annotation and branch ontool.meta is None# TwelveLabs Marengo Embed typescommentdict[str, object]cached_choice.delta.tool_callsdirectly in the cached-completion replayanthropic_content_listsfromcommon_utilsand make the router affinity check iterate through itlitellm_params,auth_type,litellm_request_debugandprovider_specific_fieldsdirectly off their typed objectsmutable-okandpyright: ignoretrailers that the gates verifiably no longer need, and narrow the two ignores that listed a rule that never firesrebind-ok, move thenoqa: PLC0415onto thefromline ruff reports, readpresidio_filter_scopeoffLitellmParams, and type the fallback chain aslist[str] | Nonelitellm_request_debugoffLoggingin the new Rust bridge callback gate, mark the bridgeTypeVarsFinal, declareNativeHookandJevProbabilityasTypeAlias, and type the member budget patch asMapping[str, object]object-valued, type the Anthropic-only tool key stripper, the aggregate authorization server metadata and the web searchtool_argsasdict[str, object], and delete the uncalledpending_daysmutable-ok/rebind-oktrailers onto the statement line the type-discipline gate reports, delete the two inertnoqa: BLE001, type the hoisted-system probe input asMapping[str, object], and readresult.next_cursoroffListToolsResultproxy_logging_obj.db_spend_update_writer.pod_lock_managerdirectly and drop the_PodLockManagerProtocol, readaudio_tokensoff the typed token-details wrappers, type the Vercel embedding error headers asdict[str, str] | httpx.Headers, and type the budget reservation lookup asdict[str, object] | NoneUser Flow
Refactor only, no user-visible behavior changes. The refusal
message_deltachunk keeps the same keys and delta contents, OTel v2 logger reuse matches the same callback names, MCP virtual tool ids resolve the same way, the Bedrock type aliases are unchanged, and encrypted-content affinity routing sees the same content blocks, OCR cost lookups, MCP pass-through admission, the Rust bridge callback gate and hosted web search replay all take the same inputs and return the same results. The 2026-09-16 run only removes or rewords comments, apart from the refusal chunk rebuild described under that date. The 2026-09-17 run touches comments and annotations only, plus one attribute read that resolves to the same value. The 2026-09-18 run is annotations only, plus one attribute read that resolves to the same value. The 2026-09-19 run is annotations only, plus the deletion of a function nothing called. The 2026-09-20 run moves or deletes comments, changes one annotation, and replaces one attribute read that resolves to the same value. The 2026-09-22 run is annotations only, plus three attribute reads that resolve to the same valuesFindings by run date
2026-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)Relevant issues
None
Affected release
Linear ticket
Pre-Submission checklist
tests/test_litellm/rust_bridge/test_dispatch.py,tests/test_litellm/rust_bridge/test_legacy_callbacks.py,tests/test_litellm/rust_bridge/test_lifecycle.py,tests/test_litellm/router_strategy/complexity_router/test_jev_classifier.py,tests/test_litellm/proxy/management_endpoints/test_common_utils.py,tests/test_litellm/proxy/test_litellm_pre_call_utils.py,tests/test_litellm/proxy/test_common_request_processing.py,tests/test_litellm/proxy/guardrails/test_init_guardrails.py,tests/test_litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py,tests/test_litellm/proxy/_experimental/mcp_server/test_oauth_identity_binding.py,tests/test_litellm/llms/anthropic/chat/guardrail_translation/,tests/test_litellm/llms/nvidia_nim/passthrough/,tests/test_litellm/proxy/management_helpers/test_bulk_user_creation.py,tests/test_litellm/proxy/management_helpers/test_bulk_user_deletion.py,tests/test_litellm/router_strategy/test_complexity_router.pyexcept the semantic-tier cases that need the optionalsemantic_routerpackage,tests/test_litellm/responses/test_streaming_iterator.py,tests/test_litellm/proxy/hooks/test_proxy_track_cost_callback.py,tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/,tests/unit/integrations/mavvrik_focus/test_mavvrik_focus_logger.py,tests/test_litellm/litellm_core_utils/llm_cost_calc/test_zero_cost_diagnostic.py,tests/unit/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py,tests/test_litellm/litellm_core_utils/test_core_helpers.py, plus the files listed by earlier runs)make lint,make checkgreen locally at 018a6a9; budgets unchanged)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
Refactor only, so the proof is the debt count each fix removes, measured at the merge base and at the tip on the four files this run touched. Command at each sha:
for f in litellm/integrations/mavvrik_focus/mavvrik_focus_logger.py litellm/litellm_core_utils/llm_cost_calc/zero_cost_diagnostic.py litellm/llms/vercel_ai_gateway/embedding/transformation.py litellm/litellm_core_utils/core_helpers.py; do git show <sha>:$f | grep -c 'getattr(\|Protocol)\|headers: Any\|) -> dict | None'; done. Earlier runs' files are unchanged since their own before/after in the PR historyBefore (25af172, merge base)
mavvrik_focus_logger.py: 3 (twogetattrreads and the_PodLockManagerProtocol)zero_cost_diagnostic.py: 1 (getattr(details, "audio_tokens", None))vercel_ai_gateway/embedding/transformation.py: 1 (headers: Any)core_helpers.py: 1 (-> dict | None)After (018a6a9, PR tip)
mavvrik_focus_logger.py: 0zero_cost_diagnostic.py: 0vercel_ai_gateway/embedding/transformation.py: 0core_helpers.py: 0make lintandmake checkpass at the tip with no budget file changesType
🧹 Refactoring
Caveats (if any)
Low
message_deltamissingusageraisedKeyErrorFinal Attestation
Note
Low Risk
Refactor-only annotation and comment cleanup with attribute reads equivalent to prior getattr paths; risk is limited to edge cases like mock-based tests (e.g. MCP list_tools cursors), which the branch already guards.
Overview
This PR is a type-discipline and lint-debt cleanup across the
litellm/tree: no intended product behavior changes beyond one fix to refusalmessage_deltarebuilding so optionalusageis not accessed unsafely.Typing and direct access: Bare
dict/Any/getattron already-narrowed types are replaced with concrete annotations (dict[str, object],Mapping[str, object], typed headers) and plain attribute reads—for examplelitellm_params, MCPauth_type/next_cursor, OTelcallback_name,provider_specific_fields, and Mavvrik pod-lock wiring without a one-offProtocol.Structure and dead code:
_anthropic_content_listsis exported asanthropic_content_listsand reused from encrypted-content affinity routing instead of duplicating the walk. Uncalledpending_daysis removed from daily global spend rollup._PodLockManageris dropped in favor of typedproxy_logging_obj.db_spend_update_writer.pod_lock_manager.Suppressions and style: Inert or mis-placed
mutable-ok/rebind-ok/noqa: BLE001/pyrighttrailers are removed or moved onto the lines gates actually report; guardrail and similar comments are trimmed where they no longer justify anything. Anthropic streaming refusal handling rebuilds the chunk via typedMessageDelta/MessageBlockDeltaspreads instead ofcast.Reviewed by Cursor Bugbot for commit 018a6a9. Bugbot is set up for automated code reviews on this repo. Configure here.
Link to Devin session: https://app.devin.ai/sessions/c477ad87a9ea40fcad90d6aadff1e3a4
Open in Devin Desktop: https://app.devin.ai/desktop/session/c477ad87a9ea40fcad90d6aadff1e3a4?variant=devin