From 7acbd8d56db3ddf4e3b41daa145755533e4c2874 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Thu, 7 May 2026 11:12:34 -0700 Subject: [PATCH] Improved StackAssertionError error logging --- .claude/CLAUDE-KNOWLEDGE.md | 3 +++ .../api/latest/connected-accounts/access-token-helpers.tsx | 5 ++--- apps/backend/src/app/api/latest/users/crud.tsx | 2 +- apps/backend/src/lib/email-rendering.tsx | 4 ++-- apps/backend/src/lib/events.tsx | 2 +- apps/backend/src/lib/js-execution.tsx | 4 ++-- apps/backend/src/lib/stripe.tsx | 2 +- apps/backend/src/oauth/providers/apple.tsx | 2 +- packages/stack-shared/src/interface/client-interface.ts | 2 +- 9 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.claude/CLAUDE-KNOWLEDGE.md b/.claude/CLAUDE-KNOWLEDGE.md index 1c3a265fa..f914d6817 100644 --- a/.claude/CLAUDE-KNOWLEDGE.md +++ b/.claude/CLAUDE-KNOWLEDGE.md @@ -403,3 +403,6 @@ A: `packages/template/vitest.config.ts` installs a Vite transform plugin for Vit ## Q: How does the Mintlify apps sidebar filter stay in sync with theme changes? A: `docs-mintlify/apps-sidebar-filter.js` injects the Apps filter with inline styles, so the MutationObserver must reapply `applySidebarAppsFilterTheme` when an existing input is found. Theme detection should handle both `html.dark` and `data-theme="dark"` signals. + +## Q: How should `StackAssertionError` preserve an underlying thrown error? +A: Pass the underlying error as the `cause` property in the second argument. The `StackAssertionError` constructor only forwards `cause` into `ErrorOptions`, so storing a caught error under an `error` property captures it as ordinary metadata instead of preserving the error cause chain. diff --git a/apps/backend/src/app/api/latest/connected-accounts/access-token-helpers.tsx b/apps/backend/src/app/api/latest/connected-accounts/access-token-helpers.tsx index dd6d365a8..eb896b5dc 100644 --- a/apps/backend/src/app/api/latest/connected-accounts/access-token-helpers.tsx +++ b/apps/backend/src/app/api/latest/connected-accounts/access-token-helpers.tsx @@ -26,7 +26,6 @@ function captureOAuthAccessTokenRefreshIssue(options: { attempts: options.refreshError.attempts, retryCount: options.refreshError.retryCount, sawAmbiguousRefreshAttempt: options.refreshError.sawAmbiguousRefreshAttempt, - error: options.refreshError.cause, causes: options.refreshError.causes, }, )); @@ -197,7 +196,7 @@ export async function retrieveOrRefreshAccessToken(options: { refreshError: tokenSetResult.error, }); const assertionError = new StackAssertionError('Unexpected error refreshing access token — this may indicate a bug or misconfiguration', { - error: tokenSetResult.error.cause, + cause: tokenSetResult.error.cause, providerClass: providerInstance.constructor.name, refreshErrorType: tokenSetResult.error.type, attempts: tokenSetResult.error.attempts, @@ -210,7 +209,7 @@ export async function retrieveOrRefreshAccessToken(options: { } default: { const _: never = tokenSetResult.error; - throw new StackAssertionError("Unhandled OAuth access token refresh error", { error: _ }); + throw new StackAssertionError("Unhandled OAuth access token refresh error", { cause: _ }); } } } diff --git a/apps/backend/src/app/api/latest/users/crud.tsx b/apps/backend/src/app/api/latest/users/crud.tsx index 66314b556..3a2443aaa 100644 --- a/apps/backend/src/app/api/latest/users/crud.tsx +++ b/apps/backend/src/app/api/latest/users/crud.tsx @@ -882,7 +882,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC } }); throw new StackAssertionError("Failed to update team member", { - error: e, + cause: e, tenancy_id: auth.tenancy.id, user_id: params.user_id, team_id: data.selected_team_id, diff --git a/apps/backend/src/lib/email-rendering.tsx b/apps/backend/src/lib/email-rendering.tsx index c9574aa43..425497c79 100644 --- a/apps/backend/src/lib/email-rendering.tsx +++ b/apps/backend/src/lib/email-rendering.tsx @@ -154,7 +154,7 @@ export async function renderEmailWithTemplate( // This can happen with complex or invalid JSX captureError("email-transpilation-template-error", new StackAssertionError( "Failed to transpile template for editable markers", - { error: e instanceof Error ? e.message : String(e) } + { cause: e } )); } } @@ -169,7 +169,7 @@ export async function renderEmailWithTemplate( // If transpilation fails, fall back to original source captureError("email-transpilation-theme-error", new StackAssertionError( "Failed to transpile theme for editable markers", - { error: e instanceof Error ? e.message : String(e) } + { cause: e } )); } } diff --git a/apps/backend/src/lib/events.tsx b/apps/backend/src/lib/events.tsx index d151feba5..b3d28ed40 100644 --- a/apps/backend/src/lib/events.tsx +++ b/apps/backend/src/lib/events.tsx @@ -247,7 +247,7 @@ export async function logEvent( data = await eventType.dataSchema.validate(data, { strict: true, stripUnknown: false }); } catch (error) { if (error instanceof yup.ValidationError) { - throw new StackAssertionError(`Invalid event data for event type: ${eventType.id}`, { eventType, data, error, originalData, originalEventTypes: eventTypes, cause: error }); + throw new StackAssertionError(`Invalid event data for event type: ${eventType.id}`, { eventType, data, originalData, originalEventTypes: eventTypes, cause: error }); } throw error; } diff --git a/apps/backend/src/lib/js-execution.tsx b/apps/backend/src/lib/js-execution.tsx index b02b822e3..f6a97ce93 100644 --- a/apps/backend/src/lib/js-execution.tsx +++ b/apps/backend/src/lib/js-execution.tsx @@ -236,7 +236,7 @@ async function runWithFallback(code: string, options: ExecuteJavascriptOptions): captureError(`js-execution-freestyle-failed`, new StackAssertionError( `JS execution freestyle engine failed, falling back to vercel sandbox engine`, - { error: retryResult.error, innerCode: code, innerOptions: options } + { cause: retryResult.error, innerCode: code, innerOptions: options } )); try { @@ -245,7 +245,7 @@ async function runWithFallback(code: string, options: ExecuteJavascriptOptions): } catch (error){ captureError(`js-execution-vercel-sandbox-failed`, new StackAssertionError( `JS execution vercel sandbox engine failed after fallback from freestyle engine`, - { error: error, innerCode: code, innerOptions: options } + { cause: error, innerCode: code, innerOptions: options } )); throw new StackAssertionError("Vercel Sandbox service unavailable", { cause: error, innerCode: code, innerOptions: options }); } diff --git a/apps/backend/src/lib/stripe.tsx b/apps/backend/src/lib/stripe.tsx index 221778332..c53ab444c 100644 --- a/apps/backend/src/lib/stripe.tsx +++ b/apps/backend/src/lib/stripe.tsx @@ -104,7 +104,7 @@ export async function resolveProductFromStripeMetadata(options: { tenancyId: options.tenancyId, productString, metadata: options.metadata, - error, + cause: error, } ); } diff --git a/apps/backend/src/oauth/providers/apple.tsx b/apps/backend/src/oauth/providers/apple.tsx index fba4ba2c0..7fb0af99a 100644 --- a/apps/backend/src/oauth/providers/apple.tsx +++ b/apps/backend/src/oauth/providers/apple.tsx @@ -35,7 +35,7 @@ export class AppleProvider extends OAuthBaseProvider { try { payload = decodeJwt(idToken); } catch (error) { - throw new StackAssertionError("Error decoding Apple ID token", { error }); + throw new StackAssertionError("Error decoding Apple ID token", { cause: error }); } return validateUserInfo({ diff --git a/packages/stack-shared/src/interface/client-interface.ts b/packages/stack-shared/src/interface/client-interface.ts index 13e5bfccd..bb964ea17 100644 --- a/packages/stack-shared/src/interface/client-interface.ts +++ b/packages/stack-shared/src/interface/client-interface.ts @@ -1562,7 +1562,7 @@ export class StackClientInterface { // refresh token was already invalid, just continue like nothing happened } else { // this should never happen - throw new StackAssertionError("Unexpected error", { error: resOrError.error }); + throw new StackAssertionError("Unexpected error", { cause: resOrError.error }); } } else { // user was signed out successfully, all good