Skip to content

fix(oauth): recover instead of locking out when a reused grant lacks event_definition:write - #1067

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixwizard-handle-missing-b687fb
Draft

fix(oauth): recover instead of locking out when a reused grant lacks event_definition:write#1067
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixwizard-handle-missing-b687fb

Conversation

@posthog

@posthog posthog Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

  • 33 distinct users (35 exceptions in 14 days, climbing 3 → 11 → 14/day, still firing on 5.48.1) are dead-ended at login and each hits it once and never returns — a near-1:1 events-to-users ratio.
  • The cohort is returning users holding a Wizard OAuth grant issued before event_definition:write became required (fix(oauth): request event definition write scope #914, 1474bc7, 2026-07-20). PostHog reuses that approval and skips the consent screen, so the new token comes back with the old, narrower scope set.
  • assertWizardCompletionScope hard-throws on the missing scope and the login call site responds with await abort() — the run dies before any setup work, and the only self-service recovery is manually revoking the authorization in PostHog settings.
  • Reached from two independent entry points (performLogin and the agent-runner authenticate path), on Windows and Linux alike.
  • Severity was being understated in triage: the capture had no $exception_fingerprint, so error tracking fingerprinted off the stack trace — which embeds each user's npx cache path and the per-release content-hashed bundle name — shattering one bug across ~10 issues, none showing more than 9 events.

Changes

  • Recover instead of abort. On a token missing event_definition:write, force the consent screen once with prompt=consent to widen the grant. OAuthConfig gains a promptConsent flag; performOAuthFlow sets prompt=consent on the authorize request when it's set.
  • Degrade if consent still can't widen it (server ignores prompt=consent, or the user declines): continue the run and skip only the end-of-session event-definition creation, with a warning — a degraded run beats a total block on onboarding.
  • Stable fingerprint on the degraded-path capture ($exception_fingerprint: 'wizard_oauth_missing_completion_scope', mirroring src/utils/oauth.ts) so it collapses into one issue that reports its real severity. Because the path no longer abort()s + process.exit()s immediately after capture, the event flushes normally with its step context instead of racing shutdown.
  • assertWizardCompletionScope is refactored onto a new non-throwing hasWizardCompletionScope; the login flow uses the latter to branch (retry → degrade) instead of the assertion's hard throw.

Note

prompt=consent is a best-effort optimization — not relied upon. If the PostHog OAuth server honors it (standard OAuth2/OIDC param), the affected user gets a fully-scoped token and full functionality restored; if not, the degrade path still unblocks the run. Either way the lockout is gone.

Test plan

  • pnpm build && pnpm test1732 pass / 122 files.
  • pnpm lint — 0 errors (only pre-existing warnings).
  • New unit tests:
    • hasWizardCompletionScope detects presence/absence without throwing.
    • Login re-requests consent (promptConsent: true) when a reused grant lacks the scope, and captures nothing when consent widens it.
    • Login degrades instead of aborting when consent still can't widen the grant — the run returns a usable project and captures once with the stable fingerprint + step/missing_scope context.

Important

Overlaps with #1060 (fix(oauth): degrade instead of dying when a scope predates its ceiling edit), which touches the same files. That PR fixes a different failure mode — a scope missing from the OAuth app's server-side ceiling, which fails the whole authorize request with invalid_scope before consent — by dropping pending scopes. This PR fixes a granted-but-narrow reused token (scope is in the ceiling; the old approval is reused and returns the narrower set), which #1060's scope-dropping does not clear. The two are complementary; whoever merges second should expect a small conflict in src/utils/oauth.ts around the authorize-URL params.

LLM context

Investigated and implemented by a PostHog coding agent (Claude). The prompt=consent round trip was not exercised against a live PostHog OAuth server; the scope-branch logic and the degrade path are unit-tested.


Created with PostHog Desktop from this inbox report.

…event_definition:write

A Wizard authorization issued before event_definition:write became required
(#914) is reused by PostHog on re-login: it skips the consent screen and
reissues the old, narrower scope set. The login flow hard-asserted the scope
and called abort(), dead-ending the run at login with a manual "revoke and
rerun" instruction.

Now the flow forces the consent screen once (prompt=consent) to widen the
grant, and if that still doesn't yield the scope it degrades — the run
continues and only the end-of-session event-definition creation is skipped.

Also fingerprint the degraded-path capture ($exception_fingerprint) so it
collapses into a single error-tracking issue instead of fragmenting across
each user's npx cache path in the stack trace.

Generated-By: PostHog Code
Task-Id: 6b4d17fb-4d90-4775-bea4-c3fd0222637c
@github-actions

github-actions Bot commented Aug 7, 2026

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 ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci mcp-analytics
  • /wizard-ci revenue
  • /wizard-ci self-driving

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/groq
  • /wizard-ci ai-observability/manual-capture
Show more apps
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /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 mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit

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