For several days, Stensibly's GitHub login was broken in a way that looked almost exactly like a network problem.
The browser could reach Stensibly. Stensibly could redirect to GitHub. GitHub could redirect back. The callback landed in the right Cloudflare Worker. Then the Worker tried to exchange the GitHub authorization code and returned:
network_exception
That left us with a depressingly wide suspect list.
Maybe GitHub was rejecting the OAuth credentials. Maybe the callback URI was subtly wrong. Maybe Cloudflare could not reach GitHub reliably. Maybe Vercel and Cloudflare were double proxying something. Maybe a timeout was too short. Maybe DNS, TLS, cache controls, redirect handling, request bodies, PKCE, or edge-specific behaviour was involved.
Most of those ideas were reasonable. None of them was the cause.
The request was not failing on the network.
The request was never leaving the Worker.
The innocent-looking line
The GitHub client supported dependency injection so tests could provide a fake fetch implementation:
this.fetchImpl = options.fetch ?? fetch;
Later it made requests like this:
await this.fetchImpl(url, init);
That looks equivalent to:
await fetch(url, init);
It is not.
In JavaScript, calling object.function() passes object as the function's this receiver. We had taken Cloudflare's native Worker fetch, stored it as a property on our GitHub client, and then invoked it as though it were a method belonging to that client.
Conceptually, we were doing something like:
nativeFetch.call(githubClient, url, init);
The production evidence strongly indicated that the Worker runtime rejected the incompatible invocation with an Illegal invocation-style TypeError before attempting the outbound request.
We deliberately did not expose raw exception messages through the OAuth callback, so that exact internal string was not retained. The causal evidence was still strong: direct Worker calls succeeded, the adapter call failed with a bounded type_error, removing request options changed nothing, and wrapping the native function fixed the next real login.
The repair was to stop storing the raw host function as a freely rebindable method. Instead, the client stores an application-owned wrapper:
const fetchImpl = options.fetch;
this.fetchImpl = fetchImpl
? (input, init) => fetchImpl(input, init)
: (input, init) => globalThis.fetch(input, init);
After that change, the next fresh login worked.
Why this took so long
The embarrassing part is that the final fix is small.
The important part is that the incident was not small.
The tests covered the request thoroughly. They checked the URL, headers, form body, PKCE verifier, redirect URI, timeout classification, provider responses, redaction, and one-shot authorization-code exchange.
But the fake fetch functions were arrow functions. Arrow functions ignore dynamic this binding. The tests therefore could not reproduce the native runtime failure.
The production probes were also convincing. A temporary Worker probe reached GitHub's token endpoint from both production origins. It used the configured OAuth credentials, the production User-Agent, and the same timeout signal. GitHub returned the expected bad_verification_code response for a deliberately invalid code.
That appeared to prove the network path was healthy.
It did prove the network path was healthy.
It did not prove the production client was invoking that network path correctly.
The probe called:
fetch(...)
Production called:
this.fetchImpl(...)
We reproduced the destination, request, credentials, and runtime. We did not reproduce the call expression.
That tiny difference was the entire bug.
The debugging ladder
The investigation only became effective after the callback stopped returning one undifferentiated failure.
First, timeout failures were separated from other exceptions. The callback still reported a non-timeout exception.
Then bounded error detail was added. The callback reported type_error.
Then an explicit operation field was added. The callback reported that the error occurred during preflight, before the single-use OAuth state was consumed and before the real authorization-code exchange.
At that point, the fault domain finally became small enough:
{
"stage": "token_exchange",
"reason": "network_exception",
"detail": "type_error",
"operation": "preflight",
"colo": "LAX"
}
Two recently added request options, cache: "no-store" and redirect: "manual", briefly became the leading suspects. Removing them was a reasonable experiment. The failure remained exactly the same.
That result mattered. It eliminated the options and forced a line-by-line comparison between the successful probe and the failing production client.
The decisive difference was not in the URL or the options object. It was the receiver attached by JavaScript's method-call syntax.
Not a Cloudflare outage, not a GitHub problem
Cloudflare's runtime exposed the sharp edge, but there is not enough evidence to call this a Cloudflare bug.
Host-provided APIs are not guaranteed to behave like arbitrary application callbacks. Browsers, Workers, databases, streams, cryptography libraries, and native bindings can care about how a function is invoked and which receiver it receives.
Our abstraction erased that distinction.
GitHub was also behaving correctly. The same Worker could reach GitHub and receive valid provider responses through direct calls. Production was fixed without changing the client ID, secret, callback URL, DNS, proxy configuration, or provider settings.
The blame belongs primarily in the application code and tests:
- a host capability was treated as a normal method;
- the dependency was typed as the whole ambient
typeof fetchobject instead of the small callable contract the client needed; - mocks modelled results without modelling native invocation semantics;
- the probe reproduced the request but bypassed the adapter.
A one-line bug and a systems failure
It is tempting to call this a simple skill issue.
At the line level, sure. Better awareness of JavaScript receivers and host functions could have prevented it.
But that description does not explain why it survived review, tests, deployment verification, and direct production probes.
The multi-day block was a systems-engineering failure:
- the error model erased the distinction between networking and local exceptions;
- the tests were realistic about data but unrealistic about runtime semantics;
- the probe was realistic about networking but unrealistic about the production call path;
- the dependency abstraction was broader than the capability actually needed;
- the only detector that exercised everything together was the real browser OAuth journey.
That is the lesson worth keeping.
Tiny code defects become expensive when every surrounding layer agrees on the wrong abstraction.
Rules worth keeping
Wrap host APIs before injecting them
Do not casually attach ambient runtime functions to arbitrary objects and invoke them as methods. Put an application-owned closure at the boundary.
Type the capability you need
The OAuth client did not need the entire platform definition of fetch. It needed this:
type RequestFunction = (
input: RequestInfo | URL,
init?: RequestInit,
) => Promise<Response>;
Smaller types make hidden assumptions harder to import.
Make probes execute the production adapter
A probe that calls the same URL is not necessarily testing the same code path. Instantiate the real class. Use the real wrapper. Preserve the same call syntax.
Test runtime semantics, not only values
Arguments, outputs, and thrown errors are not the whole contract. For host APIs, test receiver binding, request context, abort semantics, and runtime restrictions.
Keep bounded diagnostics
The final fields were useful without exposing authorization codes, tokens, credentials, provider bodies, or raw exception messages:
stage
reason
detail
operation
colo
Those fields changed the investigation from guessing about infrastructure to isolating a local call-site defect.
Real dogfood is part of verification
Metadata checks, unit tests, and synthetic probes are not substitutes for the complete user journey. The actual browser callback found a defect every lower-level check missed.
The shortest possible explanation
We took Cloudflare's native fetch, attached it to our OAuth client as though it were an ordinary method, and JavaScript called it with the OAuth client as its this receiver. The Worker rejected the invocation with a TypeError before making the network request. Our mocks and probes passed because they did not reproduce that receiver binding.
The next login worked because we finally stopped asking the Worker to make a GitHub request through a function call that was never valid in the first place.