Skip to content

Commit 0fb8631

Browse files
authored
chore(runner): expose error.cause in http client (NangoHQ#5513)
Expose error cause when http request from runner is failing. Right now we only report the parent error (`fetch failed`) which doesn't give any details about why the request failed <!-- Summary by @propel-code-bot --> BEFORE <img width="1017" height="714" alt="Screenshot 2026-02-24 at 13 56 40" src="https://github.com/user-attachments/assets/a354bf29-0f03-4e75-a761-dcdf709ac56f" /> AFTER <img width="1016" height="800" alt="Screenshot 2026-02-24 at 13 55 12" src="https://github.com/user-attachments/assets/25f1fd7c-3751-485f-942a-01e1771f10c7" /> --- It introduces an optional cause-aware serialization path through stringifyError so the runner’s HTTP client and report logger surface nested failure details in responses and logs, with accompanying tests ensuring the behavior. <details> <summary><strong>Key Changes</strong></summary> • Extended `stringifyError` signature to accept `{ cause?: boolean }` and only include the `cause` field when requested. • Updated `report` in `packages/utils/lib/errors.ts` to log errors with both stack traces and causes when Sentry is disabled. • Modified `packages/runner/lib/clients/http.ts` to include serialized `error.cause` in 502 responses returned after HTTP failures. • Added a unit test ensuring `stringifyError` includes `cause` when the new option is enabled. </details> <details> <summary><strong>Possible Issues</strong></summary> • `error.cause` may contain large or sensitive structures, so exposing it externally could require additional filtering. </details> --- *This summary was automatically generated by @propel-code-bot*
1 parent 5768b09 commit 0fb8631

3 files changed

Lines changed: 13 additions & 5 deletions

File tree

packages/runner/lib/clients/http.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ export async function httpFetch(url: string | URL, options?: HttpFetchOptions, b
8181
}
8282

8383
// Non-retryable error
84-
return new Response(JSON.stringify({ error: stringifyError(err) }), {
84+
return new Response(JSON.stringify({ error: stringifyError(err, { cause: true }) }), {
8585
status: 502,
8686
headers: { 'Content-Type': 'application/json' }
8787
});
8888
}
8989
}, backoffOptions);
9090
} catch (err) {
9191
// All retries exhausted
92-
return new Response(JSON.stringify({ error: stringifyError(err) }), {
92+
return new Response(JSON.stringify({ error: stringifyError(err, { cause: true }) }), {
9393
status: 502,
9494
headers: { 'Content-Type': 'application/json' }
9595
});

packages/utils/lib/errors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ export function errorToObject(err: unknown): ErrorObject {
2626
/**
2727
* Transform any Error or primitive to a string
2828
*/
29-
export function stringifyError(err: unknown, opts?: { pretty?: boolean; stack?: boolean }) {
29+
export function stringifyError(err: unknown, opts?: { pretty?: boolean; stack?: boolean; cause?: boolean }): string {
3030
const serialized = serializeError(err);
31-
const allowedErrorProperties = ['name', 'message', 'provider_error_payload', ...(opts?.stack ? ['stack', 'cause'] : [])];
31+
const allowedErrorProperties = ['name', 'message', 'provider_error_payload', ...(opts?.stack ? ['stack'] : []), ...(opts?.cause ? ['cause'] : [])];
3232

3333
const enriched: Record<string, unknown> = {
3434
...(serialized && typeof serialized === 'object' ? serialized : {})
@@ -88,7 +88,7 @@ export function initSentry({ dsn, hash, applicationName }: { dsn: string | undef
8888
const logger = getLogger('err');
8989
export function report(err: unknown, extra?: Record<string, unknown>) {
9090
if (!sentry) {
91-
logger.error(stringifyError(err, { stack: true, pretty: true }), extra);
91+
logger.error(stringifyError(err, { stack: true, cause: true, pretty: true }), extra);
9292
return;
9393
}
9494

packages/utils/lib/errors.unit.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ describe('stringifyError', () => {
2020
expect(parsed).toHaveProperty('stack');
2121
});
2222

23+
it('should include cause when opts.cause is true', () => {
24+
const err = new Error('Test error');
25+
err.cause = 'Underlying cause';
26+
const parsed = JSON.parse(stringifyError(err, { cause: true }));
27+
28+
expect(parsed).toHaveProperty('cause');
29+
});
30+
2331
it('should handle non-Error values without throwing', () => {
2432
expect(() => stringifyError('String error')).not.toThrow();
2533
expect(() => stringifyError(null)).not.toThrow();

0 commit comments

Comments
 (0)