Skip to content

[wrangler] Show OAuth callback errors instead of hanging on wrangler login#14022

Merged
petebacondarwin merged 3 commits into
mainfrom
fix/wrangler-oauth-callback-error-hang
Jun 3, 2026
Merged

[wrangler] Show OAuth callback errors instead of hanging on wrangler login#14022
petebacondarwin merged 3 commits into
mainfrom
fix/wrangler-oauth-callback-error-hang

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented May 23, 2026

Copy link
Copy Markdown
Contributor

When wrangler login is rejected by the Cloudflare OAuth provider — for example when the provider doesn't support a scope Wrangler is requesting — the OAuth callback redirects to http://localhost:8976/oauth/callback?error=.... Previously, for any error other than access_denied, Wrangler:

  1. Threw the OAuth error from inside the local HTTP callback handler without ever calling res.end().
  2. Called server.close(), which only fires its callback once all open connections have ended.
  3. The browser tab was still waiting for a response, so server.close() never completed.
  4. The login promise never settled, until the 120 second OAuth timeout fired with a generic "Timed out waiting for authorization code" message.

Net effect: the real OAuth error was silently swallowed and Wrangler hung for two minutes.

This PR:

  • Always writes an HTTP response on every OAuth callback exit path (a minimal inline HTML error page for non-access_denied errors, mirroring how access_denied already redirects to a hosted "consent denied" page). The same fix covers the previously-broken no-auth-code branch (// render an error page here) and a now-try/catch-wrapped token-exchange branch.

  • Reads error_description and error_uri from the OAuth callback query string (RFC 6749 §4.1.2.1) and includes them in the surfaced error so the user sees the specific reason. For example a misconfigured scope now surfaces as:

    OAuth error: invalid_scope
      The OAuth 2.0 Client is not allowed to request scope 'browser:write'.
    

    instead of hanging silently.

  • Adds a defensive if (!res.writableEnded) res.end() in finish() so a future code path that forgets to send a response can't reintroduce the hang.

  • Tightens mock-http-server.ts to faithfully reproduce production server.close() semantics (its callback only fires once res.end() has been observed). Without this, tests for the broken behaviour would pass by accident because the mock's close() fired its callback unconditionally.

  • Adds tests for invalid_scope (with and without error_description), access_denied (previously untested), and a token-exchange failure on the success path.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this is a bug fix for an error message that was previously swallowed; no user-visible documented behaviour changes.

Open in Devin Review

@changeset-bot

changeset-bot Bot commented May 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d8510ee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
wrangler Patch
@cloudflare/workers-auth Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch
@cloudflare/wrangler-bundler Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk May 23, 2026
@workers-devprod workers-devprod requested review from a team and edmundhung and removed request for a team May 23, 2026 07:30
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/wrangler-login-oauth-callback-error-hang.md: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/helpers/mock-http-server.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/helpers/mock-oauth-flow.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/user.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/user/user.ts: [@cloudflare/wrangler]

ask-bonk[bot]

This comment was marked as resolved.

@ask-bonk

ask-bonk Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

The review has been posted to PR #14022. I flagged one issue:

  • Unsafe as ErrorOAuth2 cast at user.ts:1244exchangeAuthCodeForAccessToken() can throw a plain Error (at line 935) that lacks .code and .description, causing the browser error page to render with no useful information. The suggestion is to either guard against non-ErrorOAuth2 errors at the catch site, or change line 935 to use toErrorClass() for consistency.

github run

@pkg-pr-new

pkg-pr-new Bot commented May 23, 2026

Copy link
Copy Markdown
create-cloudflare

npm i https://pkg.pr.new/cloudflare/workers-sdk/create-cloudflare@14022

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@14022

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@14022

miniflare

npm i https://pkg.pr.new/cloudflare/workers-sdk/miniflare@14022

@cloudflare/pages-shared

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/pages-shared@14022

@cloudflare/unenv-preset

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@14022

@cloudflare/vite-plugin

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/vite-plugin@14022

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/vitest-pool-workers@14022

@cloudflare/workers-auth

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-auth@14022

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-editor-shared@14022

@cloudflare/workers-utils

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/workers-utils@14022

wrangler

npm i https://pkg.pr.new/cloudflare/workers-sdk/wrangler@14022

@cloudflare/wrangler-bundler

npm i https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/wrangler-bundler@14022

commit: d8510ee

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@github-actions

github-actions Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Changeset Review

File: .changeset/wrangler-login-oauth-callback-error-hang.md

  • Version Type: patch — acceptable for a bug fix that prevents the login command from hanging and improves error visibility.
  • Changelog Quality: Meaningful description. Clearly explains the previous behavior (hanging, generic timeout), the root cause, and the improved behavior. Includes example output of the new error message.
  • Markdown Headers: No h1/h2/h3 headers found.
  • Analytics: Not applicable.
  • Dependabot: Not applicable.
  • Experimental Features: Not applicable.

✅ All changesets look good

@petebacondarwin petebacondarwin enabled auto-merge (squash) May 25, 2026 15:56
@edmundhung

Copy link
Copy Markdown
Member

@petebacondarwin Sorry for the late review! It looks like a lot of these changes are now stale as we moved the logic to the new package in #14121. Happy to give it a review right away when you resolve the conflicts 🙏🏼

… login`

When the OAuth provider redirected to `/oauth/callback` with an `error`
other than `access_denied`, Wrangler never wrote a response to the
browser. Because `server.close()`'s callback only fires once all open
connections have ended, the login command would hang until the 120s
OAuth timeout — at which point it printed a generic timeout message
rather than the actual OAuth failure. The same gap existed for the
no-auth-code case and for failures during the auth-code-to-access-token
exchange.

The OAuth provider's `error_description` (RFC 6749 §4.1.2.1) is also
now surfaced, so the user sees the specific reason rather than just the
bare `error` code.

The mock HTTP server used in unit tests is tightened to faithfully
reproduce the production `server.close()` semantics so regressions are
caught instead of papered over.
- Change `throw new Error(json.error)` in `exchangeAuthCodeForAccessToken`
  to `throw toErrorClass(json.error)` so the 2xx-with-error-body path
  produces a structured `ErrorOAuth2` like the 4xx path already does.
- Make the callback handler's token-exchange catch defensive: only read
  the structured `code`/`description` fields when the error is actually
  an `ErrorOAuth2` instance, so JSON parse failures from
  `getJSONFromResponse` and network errors from `fetchAuthToken` still
  render with a useful message rather than empty fields.
- Add a regression test for the 2xx-with-error-body path.
@petebacondarwin petebacondarwin force-pushed the fix/wrangler-oauth-callback-error-hang branch from bb8c1de to a43628c Compare June 3, 2026 12:48
@workers-devprod

workers-devprod commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

The OAuth callback fix now lives in @cloudflare/workers-auth (since
PR #14121 extracted the OAuth flow out of wrangler). Add the package
to the changeset so its CHANGELOG reflects the fix too.
Comment thread packages/workers-auth/src/callback-server.ts

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk Jun 3, 2026
@petebacondarwin petebacondarwin merged commit acf7817 into main Jun 3, 2026
73 checks passed
@petebacondarwin petebacondarwin deleted the fix/wrangler-oauth-callback-error-hang branch June 3, 2026 14:30
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants