Unreasonably Opaque GraphQL Errors (non-Error thrown as error)

I am using a module that happens to throw a Fetch API Response object as an error.
The error message in the API console is:
Unexpected error value: { size: 0, timeout: 0 }

In the interest of not having to manually wrap every service in a try-catch and handle these types of errors with a special function, I opted to attempt to install a generic error handler that would extract the JSON or text from the Response and write that to the console.

I attempted to do with with extraPlugins and was unable to do that after many hours of reading and debugging fairly complicated code across GraphQL, Yoga, and Redwood.

It seems that GraphQL itself wraps these types of errors in a NonErrorThrown type, and by the time it reaches my Envelop plugin, if I attempt to read the execution response, the NonErrorThrown: Unexpected error value: { size: 0, timeout: 0 } (with two layers of originalError) is already wiped out and replaced by “Something went wrong.”

It seems that in the past (<1yr ago) one was able to configure Redwood’s GraphQL handler with a custom formatError function that could be used to get the real error. However that is no longer exposed in favor of being able to customize “Something went wrong.” to be another constant no-information string.

Centralized, customizable error handling and rewriting seems like a fairly common need, but I don’t see a way to achieve that without just writing a hook into the code that defines the Redwood GraphQL handler, that is executed in formatError.

Am I thinking about this the wrong way? Is there an easy way to just get the actual GraphQL NonErrorThrown error and unwrap it somewhere central in my project code?

2 Likes

Great question. If @dthyresson doesn’t have the answer, he definitely knows the direction.

And you’re correct, we’ve swapped GraphQL server three times. So error handling has changed. The good news is that it keeps getting better overall. And we want to continue that trend.

Hi @twodotsmax and thanks for bringing this up – I actually noticed a little change in behavior last night as well and have a PR up that may help with your issue here: Fix to show original error in dev log when masking errors in GraphQL Server by dthyresson · Pull Request #4902 · redwoodjs/redwood · GitHub

I think what was happening is that the originalError wasn’t being included so the logger (in my case) could fetch that message.

I’d love to improve this and get your thoughts.

Would you be open to writing up an RFC as an issue with some example of how you’d like to handle errors?

The team does intend to revisit errors after v1 Launch to consider a new approach to sending the error message content (as part of the response data rather than in the “extensions”). So, we’d want to take all scenarios into account.

But back to the issue – do you think this PR may help? I want to determine why this change happened more and maybe add some tests to ensure no regressions (if possible) before merging for a patch (or other).

Thanks again.

1 Like

Will have time to provide an RFC in a bit.

I think the issue is on bleeding edge redwood, like if I yarn install I can get reasonable errors, but even like, a prisma error is going to “Something went wrong” for me on redwood main right now.

So this is a regression that happened some time after 0.49.1

Update: Confirmed that errors are completely wiped in the console in 0.50.0, so this was a regression between 0.49.1 and 0.50.0

1 Like

So, I have a fix that does not require making modifications to useRedwoodLogger or formatError.
Essentially, what I think happened was, in 0.50.0, we stopped using the useMaskedErrors Envelop plugin which before 0.50.0 was registered AFTER all the other plugins, and instead used the maskedErrors option in GraphQL Yoga, which registers that BEFORE all the other plugins outside of Yoga, which changes the format of errors consumed by the Redwood and user Envelop plugins.

If you simply revert graphql.ts to specify maskedErrors: false in the Yoga createServer call, and instead register useMaskedErrors as a plugin in Redwood’s plugin list before calling createServer with those plugins, any plugin (even user plugin) can read the errors, which I am now doing with @envelop/core’s useErrorHandler plugin, and successfully extracting the thrownValue from the NonErrorThrown generated by GraphQL.

useErrorHandler((errors: readonly GraphQLError[], context: Readonly<DefaultContext>)=>{
  return errors.forEach(err => {
    if (err.originalError && err.originalError.name === 'NonErrorThrown') {
      const oe = (err.originalError as unknown) as { thrownValue: unknown };
      nonErrorHandler(oe.thrownValue)
    }
  })
})
1 Like

Example fix here: fix error masking order for yoga graphql server by twodotsmax · Pull Request #1 · twodotsinc/redwood · GitHub

Issue created here: 0.50.0 regression where errors are masked even for the api server · Issue #4913 · redwoodjs/redwood · GitHub

Another note for future readers - I was in the middle of trying to centrally handle the non-error errors, but pulled down the latest commits from the main branch of the redwood framework, and this caused me to begin experiencing the 0.50.0 masked error regression in the middle of debugging. That is why I was at first able to get a message like “Unexpected error value: { size: 0, timeout: 0 }” which later maddeningly became ‘Something went wrong’ which I assumed was caused by my own changes

Thanks @twodotsmax – you probably saw my comments in the PR reviews. This looks great. Just getting some feedback from the Guild (so they know that using the Yoga one is a little different in the ordering) and we’ll get a patch in as soon as we can.