fix: duplicate claude code traces#29089
Conversation
Greptile SummaryThis PR fixes duplicate observability exports (Datadog, Arize, Langfuse, etc.) on async proxy streaming paths by introducing
Confidence Score: 4/5The core fix is sound and the idempotency guard covers both handler paths, but the change touches central logging infrastructure across streaming, pass-through, and deferred guardrail paths. The guard in
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/litellm_logging.py | Adds dispatch_success_handlers, _is_sync_litellm_request, and _is_assembled_stream_success; moves idempotency guard to dispatch level; changes async_success_handler to bypass should_run_logging for streaming results |
| litellm/litellm_core_utils/streaming_handler.py | Per-chunk sync logging gated behind _is_sync_litellm_request; final stream dispatches through dispatch_success_handlers instead of separate async+sync calls |
| litellm/proxy/common_request_processing.py | Deferred guardrail closure and orphaned streaming paths consolidated to use dispatch_success_handlers; removes duplicate executor.submit calls |
| tests/test_litellm/litellm_core_utils/test_litellm_logging.py | Adds four new tests covering deduplication for async, sync, prefer_async_handlers, and pass-through dispatch paths using real callbacks |
| tests/test_litellm/proxy/guardrails/test_deferred_guardrail_logging.py | Updates async_success_handler stubs to _attach_mock_success_dispatch; mock helper forwards to tracked function but omits the idempotency guard |
Reviews (5): Last reviewed commit: "changes" | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@greptileai review it |
|
@greptileai fixed, review again |
|
this pr is buns let me find a better way to do thsi |
|
@greptileai - review it, is this code quality better? |
|
@greptile-apps review this |
* [internal copy of #29089] fix: duplicate claude code traces (#29311) * refactor(proxy/auth): normalize Bearer prefix in safe-hash helper (#29343) * refactor(proxy/auth): normalize Bearer prefix in safe-hash helper UserAPIKeyAuth._safe_hash_litellm_api_key now strips a leading "Bearer "/"bearer " prefix before its existing sk-/JWT classification, so the helper produces the same hashed output regardless of whether the caller stripped the Authorization header prefix or passed the header value through unchanged. * refactor(proxy/auth): make Bearer-prefix strip case-insensitive Per RFC 7235 the HTTP authorization scheme token is case-insensitive. Replace the two-prefix loop with a single case-insensitive check so the helper normalizes "Bearer ", "bearer ", "BEARER ", and any mixed-case variant before classifying the remainder as sk- or JWT. The contract test gains coverage of "BEARER " and "BeArEr ". * test(mcp): align auth-handler test expectations with safe-hash helper The two MCP auth tests asserted that UserAPIKeyAuth(api_key="Bearer ...") retained the raw header bytes on the api_key field. _safe_hash_litellm_api_key now normalizes that input — stripping the Bearer prefix and hashing the resulting sk- key — so the expectations move to the normalized form: the bare token in the parametrize case, and hash_token("sk-...") in the backward-compat assertion. This matches what the real auth flow produces (the builder strips Bearer and the DB stores the hashed token), so the mocks now line up with production rather than with the un-normalized validator output. --------- Co-authored-by: yuneng-jiang <[email protected]>
The #29311 cherry-pick onto stable/1.85.x carried the test test_route_streaming_logging_runs_async_handler_for_sdk_passthrough, which patches PassThroughStreamingHandler._build_passthrough_logging_result to verify the SDK-passthrough dispatch contract. The manual conflict resolution kept the per-endpoint if/elif/elif chain inline in _route_streaming_logging_to_handler (matching v1.84.4's resolution), so the patched attribute did not exist and the test errored at collection with AttributeError. Extract the chain into the static _build_passthrough_logging_result helper as #29089 originally designed it. _route_streaming_logging_to_handler now resolves (standard_logging_response_object, kwargs) through the helper and dispatches via dispatch_success_handlers; the helper itself is synchronous and CPU-bound, suitable for the unit test's patch target. Verified locally: tests/pass_through_unit_tests/test_unit_test_streaming.py passes (5/5) and tests/test_litellm/litellm_core_utils/test_litellm_logging.py passes (83/83). v1.84.4 ships with the same broken test; this strictly improves on that resolution.
Relevant issues
Linear ticket
fixes LIT-3315
LIT-3414
LIT-3415
proposing a long term solution for duplicate claude code traces in datadog and arize:
Fixes duplicate observability exports (Datadog logs, OTLP traces to Arize Phoenix / Langfuse, etc.) caused by calling both async_success_handler and success_handler on the same completed stream.
Introduces dispatch_success_handlers() as the single orchestration entry for post-request success logging on proxy/streaming paths.
Adds a final-stream idempotency guard so multiple code paths that finish the same stream (CSW stream end, deferred guardrail closure, pass-through streaming) only emit one full success export per request.
Problem
For async proxy traffic (e.g. Anthropic
/v1/messagesstreaming), several places fired success logging twice:asyncio.create_task(async_success_handler(...))executor.submit(success_handler, ...)CustomLoggerintegrations normally skip sync export on async requests, but streaming deliberately disableshas_logged_async_successso per-chunk hooks can run. That left final assembled-response logging vulnerable to duplicate calls from different orchestration paths—showing up as duplicate rows in Datadog Logs Explorer and duplicate spans in OTLP backends.Callback-list dedupe alone does not fix this; the bug is when handlers run, not duplicate entries in the callback array.
Solution
add a helper function to differentiate sync and async endpoints then dispatch success handlers. this acts a single orchestartinon point that routes to the correct handler.
async calls hits acompletion -> async_success_handler()
sync (litellm sdk) hits completion -> success_handler()
pass through endpoints -> async success handler()
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Screenshots / Proof of Fix
Before:

After:

Datadog before:

after:

Type
🐛 Bug Fix
Changes