Conversation
Neither the Relay field-logger nor the message-recovery fix (prior commit) can surface the real message for most server-side field failures: async-graphql omits a failed field's key from the response entirely rather than sending it as explicit null, and Relay's normalizer only attaches an error to a record when it sees an explicit null — an absent key takes an earlier branch that warns and returns before ever consulting the error list. So relay_field_payload.error (the one Relay event that carries a message) essentially never fires for a real query-side resolver failure, and the field logger's own console output shows a path with no explanation. fetchGraphQL now reads response.errors directly off every parsed response, for every operation kind, before Relay ever touches it — sidestepping the normalizer question entirely. Each response with errors gets an immediate `[graphql-error]` console log with the full raw error list (message, path, locations, extensions), and the messages are buffered via relayFieldLogger's recordServerErrorMessages, so describeCaughtError can still recover them later for on-screen display once a generic Relay throw reaches an error boundary. This fixes the console-logging gap immediately, independent of whether @throwOnFieldError is applied to the query that failed. Query captures never throw, unlike the existing mutation path (unaffected, still throws MutationFieldError) — some field.error combinations are legitimate degradation (e.g. an absorber field resolving to null), and query components already have their own suspense/render flow to interrupt only when a boundary is meant to catch something.
This was referenced Aug 21, 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.
Neither the Relay field-logger nor the message-recovery fix (prior
commit) can surface the real message for most server-side field
failures: async-graphql omits a failed field's key from the response
entirely rather than sending it as explicit null, and Relay's
normalizer only attaches an error to a record when it sees an
explicit null — an absent key takes an earlier branch that warns and
returns before ever consulting the error list. So
relay_field_payload.error (the one Relay event that carries a
message) essentially never fires for a real query-side resolver
failure, and the field logger's own console output shows a path with
no explanation.
fetchGraphQL now reads response.errors directly off every parsed
response, for every operation kind, before Relay ever touches it —
sidestepping the normalizer question entirely. Each response with
errors gets an immediate
[graphql-error]console log with the fullraw error list (message, path, locations, extensions), and the
messages are buffered via relayFieldLogger's
recordServerErrorMessages, so describeCaughtError can still recover
them later for on-screen display once a generic Relay throw reaches
an error boundary. This fixes the console-logging gap immediately,
independent of whether @throwOnFieldError is applied to the query
that failed.
Query captures never throw, unlike the existing mutation path
(unaffected, still throws MutationFieldError) — some field.error
combinations are legitimate degradation (e.g. an absorber field
resolving to null), and query components already have their own
suspense/render flow to interrupt only when a boundary is meant to
catch something.
Stack created with Sapling. Best reviewed with ReviewStack.