Found while working #4125 — this was hidden behind a green test.
Repro
An OnException middleware whose return value is cascaded, where the returned type is also handled by the application:
public record CollidingCascade(string Text);
public static class CollidingCascadeHandler
{
public static void Handle(CollidingCascade message, CollisionRecorder recorder) { ... }
}
public static class CollidingCascadeMiddleware
{
public static CollidingCascade OnException(CollisionException ex) => new($"cascaded:{ex.Message}");
}
The generated handler for CollidingCascade fails to compile:
CS0136: A local or parameter named 'collidingCascade' cannot be declared in this scope
because that name is used in an enclosing local scope to define a local or parameter
var collidingCascade = (CollidingCascade)context.Envelope.Message; // message body
try { ... }
catch (CollisionException e)
{
var collidingCascade = middleware.OnException(e); // <-- collision
await context.EnqueueCascadingAsync(collidingCascade);
}
Cause
The OnException MethodCall's ReturnVariable takes its default name from its type, and so does the chain's message body variable. The catch block is emitted inside the scope that declares the body variable, so when a chain's input type equals an OnException return type the two locals nest and collide.
Both construction sites are affected: MiddlewarePolicy.Application.BuildOnExceptionCalls (middleware types) and the handler-type OnException loop in Chain.cs.
Why it went unnoticed
The compile failure happens at runtime, inside the handler pipeline, so it surfaces as an exception on the cascaded envelope — the message is still sent, only never executed.
CoreTests.Acceptance.on_exception_convention covers exactly this feature and was green, because both of its tests assert on session.Sent.SingleEnvelope<CascadedFromOnException>() and recorder.Actions from the first handler. Neither requires CascadedFromOnExceptionHandler to run. On top of that, both use DoNotAssertOnExceptionsDetected(), which until #4125 also suppressed the session timeout — so the failure was invisible from two directions at once.
A probe asserting the cascaded message is actually handled fails on main.
Fix
Give the OnException return variable a name that cannot collide with the enclosing scope (onException + the default name), applied at both construction sites.
Found while working #4125 — this was hidden behind a green test.
Repro
An
OnExceptionmiddleware whose return value is cascaded, where the returned type is also handled by the application:The generated handler for
CollidingCascadefails to compile:Cause
The
OnExceptionMethodCall'sReturnVariabletakes its default name from its type, and so does the chain's message body variable. The catch block is emitted inside the scope that declares the body variable, so when a chain's input type equals anOnExceptionreturn type the two locals nest and collide.Both construction sites are affected:
MiddlewarePolicy.Application.BuildOnExceptionCalls(middleware types) and the handler-typeOnExceptionloop inChain.cs.Why it went unnoticed
The compile failure happens at runtime, inside the handler pipeline, so it surfaces as an exception on the cascaded envelope — the message is still sent, only never executed.
CoreTests.Acceptance.on_exception_conventioncovers exactly this feature and was green, because both of its tests assert onsession.Sent.SingleEnvelope<CascadedFromOnException>()andrecorder.Actionsfrom the first handler. Neither requiresCascadedFromOnExceptionHandlerto run. On top of that, both useDoNotAssertOnExceptionsDetected(), which until #4125 also suppressed the session timeout — so the failure was invisible from two directions at once.A probe asserting the cascaded message is actually handled fails on
main.Fix
Give the
OnExceptionreturn variable a name that cannot collide with the enclosing scope (onException+ the default name), applied at both construction sites.