Re-attach sender wire tap to recovered envelopes (GH-3263)#3276
Merged
Conversation
…-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>
This was referenced Jun 29, 2026
Open
Merged
This was referenced Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3263
Problem
An
IWireTapregistered on a sending endpoint (AllSenders(x => x.UseWireTap(...))) never firesRecordSuccessAsyncfor 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 aScheduleAsyncmessage.Root cause
Envelope.WireTapis an in-memory-only reference — it is not serialized. The normal send path stamps it fromendpoint.WireTapinMessageRoute.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:ScheduledSendEnvelopeHandler→MessageContext.ForwardScheduledEnvelopeAsync. The recovered inner envelope has no wire tap.RecoverOutgoingMessagesCommandloads 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:
??=so an in-memory (non-recovered) forward keeps any existing tap. This mirrors exactly whatMessageRoutealready does on the normal send path.Tests
Two focused
CoreTestsreproductions (Bug_3263_wire_tap_on_scheduled_send) that simulate the durable round trip (serialize → recover → forward / enqueue) against a stub sender configured withUseWireTap:wire_tap_fires_for_scheduled_send_after_durable_recoverywire_tap_fires_for_recovered_outgoing_messageBoth 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