CxODEV-1884: carry a diagnostic message on the structured_error contract (3.8 backport) - #219
Merged
Conversation
ognjenkatic
force-pushed
the
CxODEV-1884-3.8
branch
from
August 26, 2026 11:13
699b591 to
be4b4ec
Compare
Backport of the 4.2.0 change to the 3.8 line, which consuming services pin. Identical patch: the five touched files were byte-identical between v3.8.0 and master. StructuredErrorException supported only code and reason, so callers that need both a short, stable reason and a detailed explanation had nowhere to put the detail and had to fold it into reason. That defeats matching on reason downstream, and leaves consumers with no separate diagnostic field. Add StructuredError.Message, populated from the exception. No Message property is added to the exception itself -- it already has one by virtue of being an Exception, and the new constructors set it via base(). The mapping only emits message when it differs from reason, so payloads from existing call sites are byte-identical and the field is omitted entirely (NullValueHandling.Ignore), keeping the shape at version 1. message trails referenceError in the new constructors rather than following reason. Overload resolution cannot pick between (code, reason, referenceError, message) and the existing (code, reason, referenceError, innerException) when the fourth argument is an untyped null -- and (code, reason, message, null), a message with no drill-down URI, is the common case. Placing the nullable parameter third leaves only (code, reason, referenceError, null) ambiguous, which the three-argument constructor already expresses. Both execution managers had a hand-copied exception-to-payload mapping and the test mirrored rather than called it, so a new field could pass tests while being silently dropped by the type-poll path. Extract the mapping into StructuredError.FromException and point all three at it. Released as 3.8.1 rather than a 3.8.0-suffixed build: under semver a hyphenated suffix is a pre-release and sorts before 3.8.0, so consumers on 3.8.0 would never be offered it as an upgrade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ognjenkatic
force-pushed
the
CxODEV-1884-3.8
branch
from
August 26, 2026 11:15
be4b4ec to
1275f0f
Compare
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 CxODEV-1884 for the 3.8 line. Backport of #218.
Why a separate release
Consuming services pin 3.8.0.
StructuredErrorandStructuredErrorSerializerare present in 3.8.0, absent in 4.0.0/4.0.1, and present again in 4.1.0, so shipping only onmasterwould force those consumers from 3.8.0 to 4.2.0 — a major jump across a line that already dropped and restored this contract once. This lets them pick up the fix in place whilemastergets the natural 4.2.0 progression.The patch is identical to #218: all five touched files were byte-identical between
v3.8.0andmaster(andv3.8.0is exactlyorigin/v3's tip), so this is the same change, not a re-implementation.Version: 3.8.1, not a 3.8.0 suffix
Released as 3.8.1. Under semver a hyphenated suffix is a pre-release —
3.8.0-fix1884sorts before3.8.0, so consumers on 3.8.0 would never be offered it as an upgrade, defeating the point. If a ticket reference in the version is wanted,3.8.1-fix1884sorts correctly.Bumped: Engine, Client, KafkaCancellationNotifier, Patterns. Toolkit stays at
3.0.1-beta3— it tracks separately on this branch.Change
StructuredErrorgains aMessageproperty populated from the exception. NoMessageproperty is added to the exception itself — it already has one as anException, and the new constructors set it viabase(message ?? reason).messagetrailsreferenceErrorin the new constructors: overload resolution cannot disambiguate(code, reason, referenceError, message)from the existing(code, reason, referenceError, innerException)on an untypednullfourth argument, and puttingmessagethird would place that collision on(code, reason, message, null)— the common "message, no drill-down URI" case.Existing constructors are untouched and new ones added as overloads, so existing three-argument call sites keep binding exactly as before.
messageis emitted only when it differs fromreason, so existing payloads are byte-identical and the shape stays at version 1.Both execution managers' duplicated mapping is extracted into
StructuredError.FromException, which the tests now call instead of mirroring.Scope note
Unlike
master, this branch has noSignal*infrastructure inConductorSharp.Patterns— that producer path is 4.x-only. This backport is purely the exception path, which is why it stays small.Verification
62/62 tests pass; the five changed files are csharpier-clean.
Two things worth knowing about this branch:
.github/workflows/dotnet.ymlonly triggers on PRs targetingmaster, so no CI runs here — the run above was local. And a number of pre-existing files onv3failcsharpier --check; none are touched by this PR, and no formatting sweep is included.🤖 Generated with Claude Code