Skip to content

fix: inbound replay span not sent to CLI in Django/FastAPI REPLAY mode#71

Merged
sohankshirsagar merged 1 commit intomainfrom
sohan/fix-inbound-replay-not-sent-django-fastapi
Feb 25, 2026
Merged

fix: inbound replay span not sent to CLI in Django/FastAPI REPLAY mode#71
sohankshirsagar merged 1 commit intomainfrom
sohan/fix-inbound-replay-not-sent-django-fastapi

Conversation

@sohankshirsagar
Copy link
Contributor

@sohankshirsagar sohankshirsagar commented Feb 25, 2026

Summary

  • Fix context ordering bug in Django and FastAPI REPLAY mode handlers where replay_trace_id_context was reset before span.end(), causing the SDK to silently drop inbound replay spans
  • Capture response OUTPUT_VALUE on the server span in Django's REPLAY mode so the inbound span includes actual response data

Problem

A production customer's validation run showed test results marked as "passed" with visible deviations in the UI — the "Expected vs Actual" diff panel showed the full expected response on the left and an empty {} on
the right.

Why the CLI said "passed": The CLI determines pass/fail by comparing the raw HTTP response it receives directly from the replayed request against the expected response. This comparison was correct — the responses matched.

Why the UI showed a deviation: The UI renders the diff using span_recording.outputValueRedacted (expected) vs span_result_recording.outputValueRedacted (actual). The actual side was empty because the SDK never sent the inbound replay span to the CLI, so no span_result_recording was created.

Root Causes

Bug 1: Context ordering (Django + FastAPI)

In the finally block of both _handle_replay_request handlers, replay_trace_id_context.reset() was called before span.end(). Since TdSpanProcessor.on_end() fires synchronously inside span.end() and reads replay_trace_id_context.get() to get the trace ID, the context was already gone — causing the processor to log an error and silently drop the span. The CLI never received the inbound replay span.

Bug 2: Missing OUTPUT_VALUE (Django only)

Django's _handle_replay_request did not capture response data on the span. The comment said "don't capture the span (it's already recorded)" — but the response OUTPUT_VALUE is needed on the replay span so the CLI can forward it to the backend for UI display. Added _capture_replay_output() which sets OUTPUT_VALUE on the span without the RECORD-mode concerns (transforms, trace blocking, schema merges).

Why E2E tests didn't catch this

The e2e tests only validate the passed boolean from the CLI's JSON output. Since the CLI's pass/fail is based on its own HTTP response comparison (not the inbound span data), all tests passed. The missing span data only affects the UI's "Expected vs Actual" display, which e2e tests don't verify.


Note

Medium Risk
Touches request middleware/instrumentation teardown ordering and span attribute capture, which can affect tracing/export behavior across REPLAY flows, but changes are localized and straightforward.

Overview
Fixes REPLAY-mode span exporting in Django and FastAPI by ending the server span before resetting replay_trace_id_context, ensuring TdSpanProcessor.on_end() can still route the inbound replay span to the CLI.

In Django REPLAY mode, the middleware now also captures and sets TdSpanAttributes.OUTPUT_VALUE on the server span (with HTML normalization) so the backend/UI can show the actual response in “Expected vs Actual” diffs.

Adds internal guidance to .cursor/BUGBOT.md documenting the required REPLAY ordering and OUTPUT_VALUE behavior; makes a small cleanup to the Django e2e .tusk config.

Written by Cursor Bugbot for commit 7c99402. This will update automatically on new commits. Configure here.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

@sohankshirsagar
Copy link
Contributor Author

bugbot run

@tusk-dev
Copy link

tusk-dev bot commented Feb 25, 2026

✅ Generated 18 tests - 18 passed (7c99402) View tests ↗

Tip

New to Tusk? Learn more here.

Tusk generated some tests - go to the Tusk UI to add tests.

Test Summary

  • DriftMiddleware._capture_replay_output - 6 ✅
  • DriftMiddleware._handle_replay_request - 5 ✅
  • _handle_replay_request - 7 ✅

Results

Tusk's tests all pass and directly validate the two critical bug fixes in this PR. The Django tests confirm that span.end() is called before replay_trace_id_context.reset() — fixing the context ordering bug that was silently dropping inbound replay spans. They also verify that _capture_replay_output is invoked to set OUTPUT_VALUE on the span, ensuring the CLI receives actual response data for UI diff display. The FastAPI tests validate the same context ordering fix and confirm that request/response bodies are properly captured and passed to _finalize_span. Together, these tests cover the hot path: inbound replay request handling in both frameworks, span lifecycle management, and output capture — all of which directly impact whether the CLI receives the data needed for accurate "Expected vs Actual" comparisons in the UI.


View check history

Commit Status Output Created (UTC)
7c99402 ✅ Generated 18 tests - 18 passed Tests Feb 25, 2026 7:39PM

Was Tusk helpful? Give feedback by reacting with 👍 or 👎

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@sohankshirsagar sohankshirsagar merged commit 2542778 into main Feb 25, 2026
27 checks passed
@sohankshirsagar sohankshirsagar deleted the sohan/fix-inbound-replay-not-sent-django-fastapi branch February 25, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants