Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server, orchestrator, jobs, runner): More descriptive action errors #3279

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

nalanj
Copy link
Contributor

@nalanj nalanj commented Jan 9, 2025

This adds richer errors when an action is triggered and responds with a 424 when an upstream response is the cause of an error.

Before:

❯ curl --request POST --url http://localhost:3000/action/trigger --header "Authorization: Bearer fe212ac9-0832-4210-b868-400db33b3863" --header "Connection-Id: 612d3d37-0a6c-40a5-96f2-f65df6e8fd98" --header "Provider-Config-Key: unauthenticated" --header "Content-Type: application/json" --data '{"action_name": "rate-limited"}'
{"error":{"message":"An error occurred during an HTTP call","code":"script_http_error","payload":{"error":"Rate limit exceeded"}}}%

After:

❯ curl --request POST --url http://localhost:3000/action/trigger --header "Authorization: Bearer fe212ac9-0832-4210-b868-400db33b3863" --header "Connection-Id: 612d3d37-0a6c-40a5-96f2-f65df6e8fd98" --header "Provider-Config-Key: unauthenticated" --header "Content-Type: application/json" --data '{"action_name": "rate-limited"}'
{"error":{"message":"An error occurred during an HTTP call","code":"script_http_error","payload":{"error":"Rate limit exceeded"},"additional_properties":{"upstream_response":{"status":429,"headers":{"content-type":"application/json; charset=utf-8"},"body":{"error":"Rate limit exceeded"}}}}}%

https://linear.app/nango/issue/NAN-2478/action-error-refinement

How I tested it:

  • Call an action that makes a request that errors
  • The server will return the more detailed response

Deployment

Deployment here is going to be tricky - it's all the main back-end services!

@nalanj nalanj self-assigned this Jan 9, 2025
Copy link

gitguardian bot commented Jan 10, 2025

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@nalanj nalanj changed the title WIP: More descriptive action errors feat(server, orchestrator, jobs, runner): More descriptive action errors Jan 10, 2025
@nalanj
Copy link
Contributor Author

nalanj commented Jan 10, 2025

Does anyone have any recommendations on how to document this new status code and let customers know about it? Mintlify doesn't seem to render docs for status codes other than 200 for us.

@nalanj nalanj marked this pull request as ready for review January 10, 2025 22:09
@nalanj nalanj requested a review from a team January 10, 2025 22:11
packages/runner/lib/exec.ts Show resolved Hide resolved
packages/runner/lib/exec.unit.test.ts Show resolved Hide resolved
packages/runner/lib/exec.unit.test.ts Show resolved Hide resolved
packages/shared/lib/clients/orchestrator.ts Outdated Show resolved Hide resolved
@nalanj nalanj requested review from bodinsamuel and a team January 13, 2025 12:52
@nalanj nalanj requested a review from TBonnin January 13, 2025 13:42
type: string
payload:
type: object
additional_properties:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not the upstream attribute directly instead of the additional level of nesting:

{
error:
   message:
   code:
   payload:
   upstream: {
      status_code:
      headers: 
      response:
   }
}
      

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see three directions:

  1. What you mentioned, just add more attributes as needed. My worry there was that we could end up adding more down the road, and if we do we have to update the typing for the response that comes out of actions each time. Not a big deal but it was kind of tricky to get all the types straight through all the services.
  2. We have a grab bag kind of property, additional_properties, like I built. In that case the error object stays consistent and we don't have to add all sorts of properties to it down the road. It's less pretty, but is also easier to deal with. And since it's a Record<string,unknown> it's easier to change going forward because it has to have nested properties.
  3. We shove it into payload. This one mostly doesn't work because I found cases where error payload is an array and I think remember finding other cases where it's just a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 6b665e0, but also waiting for feedback from @bodinsamuel and @khaliqgant and it's an easy thing to revert if we decide to go a different direction.

packages/runner/lib/exec.ts Show resolved Hide resolved
@@ -117,13 +118,16 @@ export class Orchestrator {
'connection.provider_config_key': connection.provider_config_key,
'connection.environment_id': connection.environment_id
};
if (!connection.id) {
throw new NangoError('invalid_input', { connection });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to throw the error instead of returning an Err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a39804e

} else {
res.status(err.status || 500).send({ error: { message: err.message, code: err.type, payload: err.payload } });
res.status(err.status || 500).send({
error: { message: err.message, code: err.type, payload: err.payload, additional_properties: err.additional_properties }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additional properties can be undefined. should we not return it at all in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just gets pruned when it's undefined, since it serializes:

❯ curl --request GET \
  --url http://localhost:3003/v1/exception \
  --header 'Authorization: Bearer fe212ac9-0832-4210-b868-400db33b3863' \
  --header 'Connection-Id: 612d3d37-0a6c-40a5-96f2-f65df6e8fd98' \
  --header 'Content-Type: application/json' \
  --header 'Provider-Config-Key: unauthenticated'
{"error":{"message":"The action script failed with a runtime error","code":"action_script_runtime_error","payload":{"error":"Something broke"}}}%

@@ -1,12 +1,14 @@
export interface RunnerOutputError {
type: string;
payload: Record<string, unknown>;
payload: Record<string, unknown> | unknown[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where can payload be an array?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a super weird one once I started cleaning up types in exec.ts.

See

return { success: false, response: null, error: { type: 'invalid_action_input', status: 400, payload: valInput } };
. valInput is a ValidateDataError[]. I'm pretty stumped as to why typescript wasn't picking that up as an error before these changes, though.

Copy link
Collaborator

@TBonnin TBonnin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me with the upstream top level attributes in the json api response.
you didn't answer this comment (it is in a resolved thread): https://github.com/NangoHQ/nango/pull/3279/files#r1913242024

@nalanj nalanj requested a review from TBonnin January 13, 2025 16:14
@nalanj
Copy link
Contributor Author

nalanj commented Jan 14, 2025

Wanted to note this is going to sit here a bit while we notify customers that it's coming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants