Skip to content

fix(OUT-3575): handle Intuit OAuth's new error format for auth errors#218

Merged
SandipBajracharya merged 3 commits intomasterfrom
OUT-3575
Apr 10, 2026
Merged

fix(OUT-3575): handle Intuit OAuth's new error format for auth errors#218
SandipBajracharya merged 3 commits intomasterfrom
OUT-3575

Conversation

@SandipBajracharya
Copy link
Copy Markdown
Collaborator

@SandipBajracharya SandipBajracharya commented Apr 10, 2026

Summary

  • Intuit silently changed their OAuth error response from Axios-style { response: { status, data } } to { error, error_description, intuit_tid }. This caused refresh token expiry errors to go undetected, preventing notification emails to IUs.
  • Added IntuitOAuthError extends Error class with a fromRaw() factory method to wrap Intuit's plain-object errors into proper Error instances. This is necessary because pRetry discards non-Error throws.
  • Added isIntuitOAuthError type guard (now instanceof based) and updated all error handling paths.
  • Extracted OAuthErrorCodes.INVALID_GRANT constant to replace magic string comparisons.

Changes

File Change
src/app/api/core/exceptions/custom.ts IntuitOAuthError class with fromRaw() factory + isIntuitOAuthError instanceof guard
src/app/api/core/utils/withErrorHandler.ts Handle IntuitOAuthError before generic Error branch + Sentry capture
src/app/api/quickbooks/auth/auth.service.ts Switch from isAxiosError to isIntuitOAuthError, use constant for invalid_grant check
src/constant/intuitErrorCode.ts Added OAuthErrorCodes with as const
src/utils/error.ts Handle IntuitOAuthError before generic Error in getMessageAndCodeFromError
src/utils/intuit.ts Wrap plain Intuit OAuth errors via IntuitOAuthError.fromRaw() in all SDK calls (_refreshAccessToken, _authorizeUri, _createToken)

Key design decisions

  • Why IntuitOAuthError extends Error?pRetry discards non-Error throws with a generic TypeError, losing all Intuit error fields. Wrapping in a proper Error subclass preserves them through the retry pipeline.
  • Why fromRaw() factory? — Keeps plain-object shape detection in one place. Callers just do throw IntuitOAuthError.fromRaw(error) ?? error.
  • Why reorder isIntuitOAuthError before instanceof Error? — Since IntuitOAuthError extends Error, the generic instanceof Error branch would catch it first, making our specific handler dead code.
  • No retry for OAuth errorsshouldRetry in withRetry.ts only retries status === 429. IntuitOAuthError has no status, so it fails fast. Network/rate-limit errors still retry normally.

Test plan

  • Simulate expired refresh token — verify turnOffSync is called and IU notification email is sent
  • Simulate non-invalid_grant OAuth error (e.g. invalid_client) — verify error is caught and reported to Sentry
  • Verify existing Axios-style errors from QuickBooks Data Services API are still handled correctly
  • Verify network errors during token refresh are still retried (up to 3 times)

🤖 Generated with Claude Code

Intuit silently changed their OAuth error response from Axios-style
`{ response: { status, data } }` to `{ error, error_description, intuit_tid }`.
This caused refresh token expiry errors to go undetected, preventing
notification emails to IUs. Add isIntuitOAuthError type guard and
update all error handling paths (withErrorHandler, auth.service, error utils).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear bot commented Apr 10, 2026

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quickbooks-sync Building Building Apr 10, 2026 8:42am
quickbooks-sync (dev) Ready Ready Preview, Comment Apr 10, 2026 8:42am

Request Review

Copy link
Copy Markdown
Collaborator

@priosshrsth priosshrsth left a comment

Choose a reason for hiding this comment

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

@SandipBajracharya Could you simplify the function to check intuit error?

…pRetry

pRetry discards non-Error throws, so the raw Intuit OAuth plain object
was being replaced with a generic TypeError before reaching our error
handlers. Wrap in IntuitOAuthError extends Error with a fromRaw() factory
method so the error fields survive the retry pipeline. Also fix branch
ordering so isIntuitOAuthError is checked before instanceof Error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@priosshrsth priosshrsth left a comment

Choose a reason for hiding this comment

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

lgtm

…oken

Apply the same IntuitOAuthError.fromRaw() wrapping to all Intuit OAuth
SDK calls so pRetry doesn't discard plain-object errors from any path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@SandipBajracharya SandipBajracharya merged commit b1517fc into master Apr 10, 2026
3 checks passed
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.

2 participants