From abf7bc0b034e4abd5a6dbbc8e0008ddd2cc25ce7 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Mon, 11 May 2026 17:25:24 +0545 Subject: [PATCH 1/2] fix(OUT-3598): embed Dropbox error_summary into Error.message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sentry uses Error.message for issue titles, so the error_summary we already pull from the content-endpoint response body was invisible there — every 409/4xx in DROPBOX-INTEGRATION-17 surfaced as opaque "Response failed with a 4xx code". Append the parsed error_summary to the constructed DropboxResponseError's .message so Sentry titles read e.g. "Response failed with a 409 code: path/not_found/..". --- src/lib/dropbox/DropboxClient.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/lib/dropbox/DropboxClient.ts b/src/lib/dropbox/DropboxClient.ts index 2ae4c32..5f2fd95 100644 --- a/src/lib/dropbox/DropboxClient.ts +++ b/src/lib/dropbox/DropboxClient.ts @@ -73,9 +73,10 @@ export class DropboxClient { // Dropbox content-endpoint errors carry their reason (e.g. `path/not_found`, // `path/restricted_content`) in the JSON response body, NOT in the headers. // We read it here so the thrown DropboxResponseError exposes the same - // `error_summary` / `error` shape the SDK would have produced — without - // this, every failure surfaces in Sentry as an opaque "Response failed with - // a 4xx code" with no way to triage. + // `error_summary` / `error` shape the SDK would have produced AND we embed + // `error_summary` into `.message` — Sentry uses Error.message for issue + // titles, so without the embedding every failure shows up as opaque + // "Response failed with a 4xx code" with no way to triage. private async buildDropboxResponseError( response: NodeFetchResponse, fallbackSummary: string, @@ -93,7 +94,18 @@ export class DropboxClient { ? parsed : { error_summary: rawBody || fallbackSummary, error: parsed } - return new DropboxResponseError(response.status, response.headers, errorPayload) + const err = new DropboxResponseError(response.status, response.headers, errorPayload) + const summary = + typeof (errorPayload as { error_summary?: unknown }).error_summary === 'string' + ? (errorPayload as { error_summary: string }).error_summary.trim() + : '' + if (summary) { + // DropboxResponseError extends Error at runtime, but its TS type def + // doesn't expose `message`; cast through Error to assign it. + const asError = err as unknown as Error + asError.message = `${asError.message}: ${summary}` + } + return err } async _getAllFilesFolders( From a014230b32679f759aae3dd1ff08727403f4e308 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Mon, 11 May 2026 17:45:56 +0545 Subject: [PATCH 2/2] fix(OUT-3598): cap embedded error_summary at 200 chars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the Dropbox content endpoint returns a non-JSON response body (e.g. a 5xx HTML page from an upstream proxy/gateway), the fallback path puts the entire raw body into error_summary — which previously ended up verbatim in Error.message and the Sentry title. Slice at 200 chars when embedding into .message; full body remains available on err.error.error_summary for structured logs. --- src/lib/dropbox/DropboxClient.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/dropbox/DropboxClient.ts b/src/lib/dropbox/DropboxClient.ts index 5f2fd95..079f742 100644 --- a/src/lib/dropbox/DropboxClient.ts +++ b/src/lib/dropbox/DropboxClient.ts @@ -95,10 +95,11 @@ export class DropboxClient { : { error_summary: rawBody || fallbackSummary, error: parsed } const err = new DropboxResponseError(response.status, response.headers, errorPayload) - const summary = - typeof (errorPayload as { error_summary?: unknown }).error_summary === 'string' - ? (errorPayload as { error_summary: string }).error_summary.trim() - : '' + const rawSummary = (errorPayload as { error_summary?: unknown }).error_summary + // Cap at 200 chars so non-JSON proxy/gateway error bodies (e.g. a 5xx + // HTML page from a load balancer) don't blow up the Sentry title. Full + // body remains available on `err.error.error_summary` for structured logs. + const summary = typeof rawSummary === 'string' ? rawSummary.trim().slice(0, 200) : '' if (summary) { // DropboxResponseError extends Error at runtime, but its TS type def // doesn't expose `message`; cast through Error to assign it.