Skip to content

Re-attach sender wire tap to recovered envelopes (GH-3263)#3276

Merged
jeremydmiller merged 1 commit into
mainfrom
fix/wire-tap-scheduled-send-3263
Jun 29, 2026
Merged

Re-attach sender wire tap to recovered envelopes (GH-3263)#3276
jeremydmiller merged 1 commit into
mainfrom
fix/wire-tap-scheduled-send-3263

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3263

Problem

An IWireTap registered on a sending endpoint (AllSenders(x => x.UseWireTap(...))) never fires RecordSuccessAsync for a scheduled message sent to a transport that does not support native scheduled send (e.g. Kafka). The user sees the send-side audit log for an immediate publish, but not for a ScheduleAsync message.

Root cause

Envelope.WireTap is an in-memory-only reference — it is not serialized. The normal send path stamps it from endpoint.WireTap in MessageRoute.CreateForSending, which is why immediate sends tap correctly.

But any envelope that is persisted and later recovered comes back with WireTap == null, and the recovery code rebuilds the sender/serializer without re-attaching the tap:

  • Scheduled sends (the issue): a scheduled send to a non-native-scheduling transport is wrapped into a durable "scheduled-envelope", persisted, and later recovered + forwarded by ScheduledSendEnvelopeHandlerMessageContext.ForwardScheduledEnvelopeAsync. The recovered inner envelope has no wire tap.
  • Durable outbox recovery (same defect, broader trigger): RecoverOutgoingMessagesCommand loads persisted outgoing envelopes after a restart / broker outage and enqueues them directly — these also have no wire tap.

Fix

Re-attach the wire tap from the resolved sending endpoint in both recovery paths:

envelope.WireTap ??= envelope.Sender.Endpoint.WireTap;       // ForwardScheduledEnvelopeAsync
envelope.WireTap ??= _sendingAgent.Endpoint.WireTap;          // RecoverOutgoingMessagesCommand

??= so an in-memory (non-recovered) forward keeps any existing tap. This mirrors exactly what MessageRoute already does on the normal send path.

Tests

Two focused CoreTests reproductions (Bug_3263_wire_tap_on_scheduled_send) that simulate the durable round trip (serialize → recover → forward / enqueue) against a stub sender configured with UseWireTap:

  1. wire_tap_fires_for_scheduled_send_after_durable_recovery
  2. wire_tap_fires_for_recovered_outgoing_message

Both fail without the fix (asserted by reverting) and pass with it. The existing wire-tap and durability suites stay green. No broker/DB required.

🤖 Generated with Claude Code

…-3263)

IWireTap registered on a sending endpoint never fired RecordSuccessAsync for
messages that went through durable storage before being sent — most visibly
scheduled sends to a non-native-scheduling transport (e.g. Kafka), as reported
in #3263.

Root cause: Envelope.WireTap is an in-memory-only reference (not serialized).
The normal send path stamps it from endpoint.WireTap in MessageRoute, which is
why immediate sends tap correctly. But any envelope that is persisted and later
recovered comes back with WireTap == null, and the recovery code rebuilds the
sender/serializer without re-attaching the wire tap:

  - ScheduledSendEnvelopeHandler -> MessageContext.ForwardScheduledEnvelopeAsync
    (the scheduled-send case in the issue)
  - RecoverOutgoingMessagesCommand (durable outbox recovery after a restart /
    broker outage — same defect, broader trigger)

Both now re-attach the wire tap from the resolved sending endpoint
(`WireTap ??= ...Endpoint.WireTap`, so an in-memory forward keeps any existing
tap).

Adds focused CoreTests reproductions for both paths that simulate the durable
round trip (serialize -> recover -> forward/enqueue) against a stub sender with
UseWireTap; both fail without the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 4e6fccb into main Jun 29, 2026
26 checks passed
This was referenced Jun 29, 2026
This was referenced Jul 9, 2026
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.

When publishing a deferred message to kafka, WireTap does not work.

1 participant