Skip to content

fix: surface actionable OAuth token exchange errors#458

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/fix-oauth-400-error-ux
Draft

fix: surface actionable OAuth token exchange errors#458
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/fix-oauth-400-error-ux

Conversation

@posthog
Copy link
Copy Markdown

@posthog posthog Bot commented May 19, 2026

Problem

When the OAuth token exchange at POSTHOG_OAUTH_URL/oauth/token returns 400, the user sees Authorization failed: Request failed with status code 400 and the wizard aborts with no path to recover. This is the very first step of npx @posthog/wizard, so when it triggers it's a hard block.

The catch in performOAuthFlow (src/utils/oauth.ts) only branched on 'timeout' and 'access_denied' substrings. Any 400 from the token endpoint — invalid_grant (expired/already-used code), PKCE code_verifier mismatch, redirect_uri mismatch — fell through to the generic message and dropped the OAuth payload on the floor. The 401 UX added in #432 didn't cover 400.

Surfaced by error tracking issue ffce0351…, first seen 2026-05-19 (currently 1 user / 1 occurrence over 30 days but P2 because it blocks the install entirely).

Changes

  • New OAuthTokenExchangeError in src/utils/oauth.ts that carries the OAuth error code, error_description, and HTTP status (RFC 6749 §5.2).
  • exchangeCodeForToken wraps the axios.post and, on axios.isAxiosError, extracts response.data.error / error_description and throws the typed error instead of letting the raw AxiosError escape.
  • performOAuthFlow's catch now branches on OAuthTokenExchangeError first and renders a code-specific message via describeOAuthTokenExchangeError:
    • invalid_grant → "the authorization code was rejected... Please re-run the wizard to start a fresh login"
    • invalid_request / invalid_client / unauthorized_client / unsupported_grant_type / invalid_scope → labeled message + error_description + issue-tracker link so a user filing a bug includes the diagnostic detail
    • unknown code → "Authorization failed ()" with description
  • Analytics now tags oauth_error and oauth_status on captured exceptions for these failures, so similar reports are easier to group.

The typed check runs before the existing timeout / access_denied substring checks so a 400 whose description happens to contain those words doesn't get mis-routed.

Test plan

  • pnpm build — passes
  • pnpm test — 611 passed, 0 failed (the earlier flaky provision-cli timeout cleared on re-run; unrelated to this change)
  • pnpm lint — 0 errors

LLM context

Co-authored by PostHog Code in response to error tracking issue ffce0351… (first occurrence 2026-05-19). Investigation traced the AxiosError through performOAuthFlowaskForWizardLogingetOrAskForProjectData.


Created with PostHog Code

Token exchange in `exchangeCodeForToken` previously bubbled raw
`AxiosError` instances out of `performOAuthFlow`, whose catch only
branched on `timeout` and `access_denied`. Any 400 — `invalid_grant`
from an expired/already-used code, `invalid_request` from a redirect
URI or PKCE mismatch — surfaced as `Authorization failed: Request
failed with status code 400` with no path to recover.

Wrap the axios call to extract `error` and `error_description` from
the OAuth payload (RFC 6749 §5.2) and re-throw a typed
`OAuthTokenExchangeError`. `performOAuthFlow` then renders a code-
specific message: `invalid_grant` tells the user to re-run the wizard,
the rest direct them to the issue tracker with the description text.

Generated-By: PostHog Code
Task-Id: 6df20624-24ad-42c1-9e75-6ce6217c0831
@github-actions
Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci basic-integration
  • /wizard-ci misc
  • /wizard-ci revenue

Test an individual app:

  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
Show more apps
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci misc/quack-quack
  • /wizard-ci revenue/stripe

Results will be posted here when complete.

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.

0 participants