Skip to content

fix(bluesky): Sign in with Bluesky on Workers — translate resolver redirect:'error' → 'manual'#47

Merged
MartinMontero merged 2 commits into
mainfrom
claude/wcjbt-bsky-oauth-workers-redirect
Jul 4, 2026
Merged

fix(bluesky): Sign in with Bluesky on Workers — translate resolver redirect:'error' → 'manual'#47
MartinMontero merged 2 commits into
mainfrom
claude/wcjbt-bsky-oauth-workers-redirect

Conversation

@MartinMontero

Copy link
Copy Markdown
Owner

What does this PR do?

Fixes the live prod defect: Sign in with Bluesky failed for every user, deterministically, with "Sign-in didn't finish — please try again." Nostr sign-in was unaffected.

Two commits:

  1. fix(bluesky) — the root-cause fix + a regression test.
  2. observability(bluesky) — un-swallow the bare catch {} that hid the failure.

Root cause (confirmed, not inferred)

@atproto/oauth-client@0.7.3's identity/handle/DID resolvers (did:web, did:plc, the XRPC and well-known handle resolvers) all issue fetch(url, { redirect: 'error' }). Cloudflare's edge runtime rejects redirect: 'error' with TypeError: … "error" won't be implemented … at the edge; use "manual" and check the response status code. So authorize() threw at the identity-resolution phase — before the browser ever left the site, before any PDS contact — and the handler's bare catch {} reduced it to a generic banner. (Upstream: bluesky-social/atproto#3292.)

Evidence table (all read-only, pre-fix):

Signal Finding
Failure locus ?bsky=error&reason=authorize, deterministic across two handles → start-side throw
Config plane client-metadata content-correct; redirect_uris byte-match; BLUESKY_PRIVATE_KEY_JWK loads as valid ES256; general Worker egress works (npmjs)
State ATPROTO KV holds zero bsky_state: entries → throw precedes state-save
Captured exception (wrangler dev) Failed to resolve identity → cause TypeError: Invalid redirect value … at the edge

The fix

Inject an edgeFetch as the OAuthClient's Fetch (which it threads to every resolver): it rewrites redirect: 'error'redirect: 'manual' on the init before any Request is constructed — constructing one with 'error' is itself what throws. The resolvers already reject any non-200 response, and a manual-mode redirect surfaces as a non-200 opaqueredirect, so the "fail on an unexpected redirect" security intent is preserved. Zero new dependencies. The single makeClient factory means the callback token-exchange path is covered by the same fix.

Verification

  • Local (wrangler dev), the confirming transition: before the fix, the captured cause is the redirect TypeError; after the fix, that TypeError is gone and the flow proceeds to the real network call (which fails here only because this diagnosis container's egress allowlist blocks bsky.social — a constraint production does not have). That transition is the proof the redirect barrier is cleared.
  • Unit test pins the redirect rewrite ('error''manual') and passthrough of every other init/no-init case.
  • What a unit test cannot cover (stated plainly): the live atproto resolver + PDS round-trip on the Workers runtime — that is your prod re-test.
  • verify:all green; enforce green; 285 tests, 0 fail.

Type of change

  • Enforcement engine / tooling
  • Other — Worker auth runtime fix (Cloudflare edge compatibility)

Required checks

  • npm run check · [x] npm run enforce · [x] npm test (285) · [x] npm run build — all green locally.

CI is pull_request-only, so this draft PR is these commits' first CI run.

For catalog entries / recipes

Not applicable.

Deploy semantics & your post-merge re-test

Workers Builds is live — merging this IS a production deploy. After you merge, please re-test on prod:

  1. Sign in with Bluesky end-to-end (the original failing action) → lands signed in.
  2. Sign in with Nostr still works (the start handler was touched).
  3. Clean sign-out.

I'll then re-run the read-only checks (admin negative contract unchanged; /console/ CSP single-header; status endpoints 200), and admin smoke [3] (Bluesky reuse-then-elevate) is unblocked pending identities.

Draft on purpose — do not mark ready-for-review, do not merge; merge is yours alone and is a production deploy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP


Generated by Claude Code

claude added 2 commits July 4, 2026 04:37
…Workers edge

Sign in with Bluesky failed on prod for every handle, deterministically, before
the browser ever left the site (?bsky=error&reason=authorize). Root cause,
confirmed by capturing the swallowed exception under wrangler dev:
@atproto/oauth-client's identity/handle/DID resolvers (did:web, did:plc, and the
XRPC + well-known handle resolvers) all issue fetch(url, { redirect: 'error' }),
and Cloudflare's edge runtime rejects redirect:'error' with a TypeError
("won't be implemented ... at the edge; use manual and check the response status
code") — so authorize() throws at identity resolution, before reaching the PDS.
See github.com/bluesky-social/atproto#3292.

Fix: inject an edgeFetch as the OAuthClient's Fetch — OAuthClient threads it to
every resolver it builds — that rewrites redirect:'error' to redirect:'manual' on
the init BEFORE any Request is constructed (constructing one with 'error' is
itself what throws). The resolvers already reject any non-200 response, and a
manual-mode redirect surfaces as a non-200 opaqueredirect, so the "fail on an
unexpected redirect" security intent is preserved. Zero new dependencies; the
single makeClient factory means the callback token-exchange path is covered too.

Verified under wrangler dev: with the fix the captured failure moves PAST the
redirect TypeError to the real network call (blocked only by this container's
egress allowlist, which production does not have). Full end-to-end success is the
operator's prod re-test. The unit test pins the redirect rewrite + passthrough;
the live PDS round-trip cannot run in the offline fake-KV harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP
…ng it

The start handler caught blueskyAuthorizeUrl()'s error with a bare `catch {}` and
showed only a generic banner — which is exactly why the redirect:'error' defect
fixed in the preceding commit was invisible to anyone diagnosing it. Log the
error name/message/cause so it surfaces in Workers Logs (observability is already
enabled in wrangler.jsonc). This pre-authorization path handles no token, cookie,
or key material, so there is nothing secret to redact; the user still sees only
the generic bsky=error banner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
wecanjustbuildthings 092e57d Jul 04 2026, 04:40 AM

@MartinMontero
MartinMontero marked this pull request as ready for review July 4, 2026 04:52
@MartinMontero
MartinMontero merged commit 39328a2 into main Jul 4, 2026
14 checks passed
@MartinMontero
MartinMontero deleted the claude/wcjbt-bsky-oauth-workers-redirect branch July 4, 2026 04:52
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