Skip to content

fix: prevent [object Object] in API error messages - #234

Merged
jlau-stripe merged 1 commit into
mainfrom
jlau/better-error-handling
Aug 7, 2026
Merged

fix: prevent [object Object] in API error messages#234
jlau-stripe merged 1 commit into
mainfrom
jlau/better-error-handling

Conversation

@jlau-stripe

@jlau-stripe jlau-stripe commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

When the API returns an error in the nested object shape { "error": { "message": "invalid scope: something" } }, several places in the codebase would coerce that object directly into a string — producing [object Object] in the terminal instead of the actual error message.

1 Example:

Before:

632436803-9198e1fd-1a4a-4ad5-9acc-2586697d6bd9

After:

CleanShot 2026-08-06 at 16 16 22

Root causes fixed

  1. Auth resources (CLI + SDK) — formatOAuthError interpolated err.error directly. Since err.error can be an object, this produced [object Object]. Fixed by adding extractOAuthErrorMessage which checks typeof err.error at runtime before extracting
    the message.
  2. pollDeviceAuth switch bug — switch (err.error) was comparing a string | object union against string case labels. When error was an object, authorization_pending and slow_down never matched, causing the polling loop to throw instead of
    continuing. Fixed by switching on extractOAuthErrorCode(err) (returns string | undefined).
  3. error_description regression — The new extractOAuthErrorMessage used a truthy check (if (err.error_description)) instead of nullish (!= null), silently dropping an empty-string description and falling back to the error code instead. Fixed to
    match the prior ?? behavior.
  4. Four SDK resources — payment-methods, shipping-address, user-info, and web-bot-auth extracted errors with (body?.error as string | undefined) ?? — a TypeScript-only cast with no runtime guard. A nested object error passed the ?? check and got
    coerced. Fixed by replacing the inline pattern with extractErrorMessage(data, rawBody) from base.ts, which already handles both shapes correctly.
  5. useAsyncAction hook — String(err) for non-Error throwables produces [object Object] for plain objects. Fixed to JSON.stringify(err).

- Handle nested `{ error: { message } }` error shape in auth resources
  (both CLI and SDK implementations) by replacing the cast-only
  `err?.error` interpolation with `extractOAuthErrorMessage`, which
  does a runtime typeof check before descending into the object
- Fix `switch (err.error)` in `pollDeviceAuth` to switch on
  `extractOAuthErrorCode(err)` so polling continues correctly when a
  non-string error arrives (object would never match a string case)
- Fix `error_description` truthy guard to a nullish check so an empty
  string from the server is preserved rather than falling back to the
  error code
- Replace inline cast-only error extraction in payment-methods,
  shipping-address, user-info, and web-bot-auth resources with
  `extractErrorMessage` from base.ts, which already handles both
  string and nested-object error shapes
- Replace `String(err)` in `useAsyncAction` with `JSON.stringify(err)`
  so thrown plain objects produce readable output instead of
  [object Object]
- Add tests covering all of the above

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jlau-stripe
jlau-stripe merged commit b3493d2 into main Aug 7, 2026
4 checks passed
@jlau-stripe
jlau-stripe deleted the jlau/better-error-handling branch August 7, 2026 14:11
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