Skip to content

Fix 6.4.0 NRE on empty system envelope (BufferedReceiver + MoveToErrorQueue null guards); bump 6.4.1#3014

Merged
jeremydmiller merged 1 commit into
mainfrom
fix-3013-empty-envelope-nre
Jun 3, 2026
Merged

Fix 6.4.0 NRE on empty system envelope (BufferedReceiver + MoveToErrorQueue null guards); bump 6.4.1#3014
jeremydmiller merged 1 commit into
mainfrom
fix-3013-empty-envelope-nre

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Fixes #3013.

The empty all-zero (#00000000-0000-0000-0000-000000000000) system / agent-handshake envelope that the 6.4.0 startup path emits tripped two unguarded null-forgiving derefs (introduced in e0b67fb "Fix compiler warnings"), producing cascading NullReferenceExceptions that aggregated into every consumer integration test going through a TrackedSession — even when the test's own assertions would have passed.

Fixes

  • BufferedReceiver.cs_deferBlock / _completeBlock retry lambdas dereferenced env.Listener!. Now env.Listener is { } l ? l.DeferAsync(...) : Task.CompletedTask — a listener-less envelope no-ops instead of NRE-ing into the retry loop.
  • MoveToErrorQueue.csvar scheme = lifecycle.Envelope!.Destination!.Scheme; was evaluated before the EnableAutomaticFailureAcks guard, so consumers who explicitly disable failure acks still NRE'd. Now lifecycle.Envelope!.Destination?.Scheme + a scheme is not null guard. (Only Destination is guarded — the envelope itself is always present, as the block just below already relies on; guarding Envelope too would trip CS8602 on those derefs.)

Steady-state behaviour is unchanged — both guards only no-op the malformed pre-init envelope.

Regression tests

Both fail without the fix and pass with it (verified by reverting the two source files):

  • MoveToErrorQueueTester.does_not_NRE_when_envelope_has_no_destination — destination-less envelope with failure acks enabled completes without NRE, sends no failure ack, still moves to DLQ.
  • buffered_receiver_null_listener_guard_3013 — real host + a capturing ILoggerProvider; posts a listener-less envelope to both retry blocks and asserts no NullReferenceException is logged (RetryBlock swallows + logs + retries the exception rather than rethrowing, so a "does not throw" assertion wouldn't catch it).

Release

Bumps Directory.Build.props to 6.4.1. Full wolverine.slnx Release build clean (0/0); CoreTests serialization/error-handling suites green.

🤖 Generated with Claude Code

…s; bump to 6.4.1 (GH-3013)

The empty all-zero system/agent-handshake envelope emitted on the 6.4.0 startup path
tripped two unguarded null-forgiving derefs, producing cascading NREs that aggregated
into every consumer integration test going through a TrackedSession:

- BufferedReceiver's _deferBlock/_completeBlock retry lambdas dereferenced env.Listener!
  with no null check. Now no-op (Task.CompletedTask) when Listener is null.
- MoveToErrorQueue.ExecuteAsync read lifecycle.Envelope!.Destination!.Scheme *before* the
  EnableAutomaticFailureAcks guard, so even consumers disabling failure acks still NRE'd.
  Destination is now read safely (scheme null => no failure ack); the envelope itself is
  always present, so only Destination is guarded.

Steady-state behaviour is unchanged — both guards only no-op the malformed pre-init envelope.

Regression tests (both fail without the fix, pass with it):
- MoveToErrorQueueTester.does_not_NRE_when_envelope_has_no_destination
- buffered_receiver_null_listener_guard_3013 (real host + capturing logger; asserts no NRE is
  logged when a listener-less envelope is posted to both retry blocks, since RetryBlock swallows
  + logs the exception rather than rethrowing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 7253408 into main Jun 3, 2026
23 of 24 checks passed
This was referenced Jun 3, 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.

6.4.0 NRE on empty system envelope — BufferedReceiver._deferBlock / _completeBlock + MoveToErrorQueue.ExecuteAsync need null guards

1 participant