tinyhumansai/openhuman · error · Error
OpenRouter OAuth returned an invalid callback URL.
Error message
OpenRouter OAuth returned an invalid callback URL.
What it means
extractOAuthCode runs new URL(callbackUrl) inside try/catch; anything the WHATWG URL constructor cannot parse (empty string, relative path, fragment-only garbage captured from the loopback listener) throws and is rethrown as this message.
Source
Thrown at app/src/utils/openrouterOAuth.ts:50
function base64UrlEncode(bytes: Uint8Array): string {
let binary = '';
for (const value of bytes) {
binary += String.fromCharCode(value);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
async function createCodeChallenge(verifier: string): Promise<string> {
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
return base64UrlEncode(new Uint8Array(digest));
}
function extractOAuthCode(callbackUrl: string, expectedState: string): string {
let parsed: URL;
try {
parsed = new URL(callbackUrl);
} catch {
throw new Error('OpenRouter OAuth returned an invalid callback URL.');
}
const actualState = parsed.searchParams.get('state');
if (actualState !== expectedState) {
throw new Error('OpenRouter OAuth callback state did not match the request.');
}
const code = parsed.searchParams.get('code');
if (!code) {
throw new Error('OpenRouter OAuth did not return an authorization code.');
}
return code;
}
async function exchangeCodeForKey(
code: string,
verifier: string,
fetchImpl: typeof fetchView on GitHub (pinned to a221052e0d)
Solutions
- Log the raw callbackUrl — it is usually empty or a fragment
- Make the listener skip requests whose path does not match the expected callback path
- If empty captures recur, keep listening for the next request instead of failing on the first
Example fix
// before — first captured request decides
const url = await listener.next();
const code = extractOAuthCode(url, state);
// after — only requests that parse and match the path count
for await (const url of listener) {
if (!url.startsWith('/callback')) continue;
const code = extractOAuthCode(url, state);
break;
} Defensive patterns
Strategy: validation
Validate before calling
function isParsableUrl(u: string): boolean {
try {
new URL(u);
return true;
} catch {
return false;
}
} Try / catch
Catch the parse failure, keep the listener open, and wait for the next captured request — the first request is often a favicon or probe, not the OAuth callback.
Prevention
- Filter loopback requests by path before treating any as the callback
- Never assume the first captured request is the redirect
- Propagate listener socket errors instead of coalescing them to empty strings
When it happens
Trigger: The loopback OAuth listener hands back an empty or malformed capture — the socket closed before the request line was read, or the first non-OAuth request (favicon, probe) was mistaken for the callback.
Common situations: Browser firing an extra request first; listener race where the connection reset mid-read; port-fallback logic returning an incomplete string.
Related errors
- OpenRouter OAuth listener returned an invalid redirect URL.
- OpenRouter OAuth requires the desktop app. Use an API key in
- OpenRouter OAuth callback state did not match the request.
- OpenRouter OAuth did not return an authorization code.
- OpenRouter key exchange failed (${response.status}).
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/10449c37bb07681b.
Report an issue: GitHub.