Uh oh!
There was an error while loading. Please reload this page.
CxODEV-1884: carry a diagnostic message on the structured_error contract - #218
Merged
Conversation
ognjenkaticforce-pushed
the
CxODEV-1884-message
branch
from
August 26, 2026 11:13
cb208e7 to
40abcc3CompareStructuredErrorException 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. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ognjenkaticforce-pushed
the
CxODEV-1884-message
branch
from
August 26, 2026 11:15
40abcc3 to
3776652CompareUh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Problem
StructuredErrorExceptioncarries onlycodeandreason. A worker that needs both a short, stable reason and a detailed explanation has nowhere to put the detail, so it gets folded intoreason. That makesreasonunusable as something consumers can match on, and leaves them with no separate diagnostic field.Change
StructuredErrorgains aMessageproperty, populated from the exception.No
Messageproperty is added to the exception. It already has one by virtue of being anException; the two new constructors set it throughbase(message ?? reason). Adding a shadowing property would have split one name across two values selected by static type — the catch block readsexception.Messageoff the base type three lines from where it narrows toStructuredErrorException.The detail has to live inside the
structured_errorsubtree to be reachable at all.TryParsepresence-checks that single key and deserializes nothing else, so consumers never see the siblingerror_messagewhen a structured payload is present.Parameter order
messagetrailsreferenceErrorrather than followingreason. Overload resolution cannot choose between(code, reason, referenceError, message)and the existing(code, reason, referenceError, innerException)when the fourth argument is an untypednull:With
messagein third position that fires on(code, reason, message, null)— a message with no drill-down URI, which is the common case. Putting the nullable parameter third instead leaves only(code, reason, referenceError, null)ambiguous, an explicitly-null inner exception that the three-argument constructor already expresses. The reasoning is in the XML doc so it does not get "fixed" back.Backward compatibility
messageonly when it differs fromreason, so payloads from existing call sites are byte-identical.NullValueHandling.Ignoreomits the key entirely when unset;MissingMemberHandlingdefaults to Ignore, so older consumers skip it without throwing.CurrentVersionstays at 1. The field is purely additive, and bumping it would be harmful —TryParseignores version but a consumer may not.messagewould have silently rebound every three-argument call site, sending thereferenceErrorURI intomessagewith no compiler complaint.Deduplication
Both execution managers carried a hand-copied exception-to-payload mapping, and the test mirrored it rather than calling it — so a new field could pass tests while being silently dropped by the type-poll path. Extracted into
StructuredError.FromException; both managers and the tests now call it.Verification
62/62 tests pass (
dotnet test ConductorSharp.sln), full solution builds with no new warnings. New coverage:messagepresent under the snake_case key, omitted when unsupplied, omitted when it only repeatsreason, surviving theTryParseround trip, reachingerror_message/ReasonForIncompletion, and amessage-only payload still degrading to the generic path.Related
Ships alongside #219, which backports the identical patch to the 3.8 line (3.8.1) for consumers pinned there. The five touched files were byte-identical between
v3.8.0andmaster.Note for signal-based producers:
StructuredErrorSerializer.ToOutputDatapicks the new field up automatically, but callers that build the payload themselves must populate it.🤖 Generated with Claude Code